React 状态管理:从现在开始拥抱redux-toolkit
在之前的文章:从零开始学习React-5:状态与状态管理,我们简单的介绍目前最流行的状态管理库 redux。今天我们介绍由 redux 团队推出的,可以更高效、更方便使用 redux 的另一个工具:Redux-Toolkit(简称:RTK)
为什么是 redux-toolkit
redux 被设计的非常灵活,这也带来了一些问题,例如如果我们直接使用 redux 会有如下几点不便之处:
-
模板代码太多
在上一篇文章示例中可能感觉还不明显,其实正常项目中,每一个 Action 一般都会有这样几个模板代码:
- 声明一个静态的 type 字符串,例如
const ADD_TODO = 'addTodo';
- 声明一个 action creator 函数,例如
export const addTodo = (data) => ({ type: ADD_TODO, data });
- reducer 函数内需要根据 action 的 type 写
switch - case
action 对象的 type 一般都是唯一的一个字符串,因为 dispatch 一个 action 时,所有的 reducer 函数都会收到。
这带来了一个隐藏的问题,如果我们在多个 reducer 函数中 case 了 一个相同的 type 字符串,这两个 reducer 都会执行动作,改变状态。
这也就是为什么 Redux 推荐我们使用 action creator 函数,这可以有效的避免写错导致bug。
- 声明一个静态的 type 字符串,例如
-
仅安装 redux 可能无法胜任。我们一般还需要安装 比如 redux-thunk(异步)、immer(状态不可变) 等库来配合 redux 工作。
这就导致了 redux 的配置比较复杂,很难做到开箱即用,即使有的配置已经成为了某种意义上的 “ 最佳实践 ”。
RTK 通过几个函数很好的帮我们解决了这些细节问题,几乎做到了开箱即用。
如何使用 RTK
这里我们还是使用之前文章的 TodoList 来作为例子,我们将它修改成使用 RTK 实现。
-
安装 RTK :
yarn add @reduxjs/toolkit react-redux
查看其依赖我们可以看到 RTK 已经依赖了 redux 与 redux-thunk了
"dependencies": { "immer": "^9.0.7", "redux": "^4.1.2", "redux-thunk": "^2.4.1", "reselect": "^4.1.5" },
-
创建
todo.slice.js
Slice 切片
import { createSlice } from "@reduxjs/toolkit";
const initialState = [];
// 固定格式,创建 slice 对象
const todoSlice = createSlice({
name: 'todo',
initialState,
// 相当于原来reducer中的case
reducers: {
// 后面文中我们将这类函数称之为 case
addTodo: (state, action) => {
state.push(action.payload);
},
delTodo: (state, action) => {
// 这里要注意不能使用之前的filter方式,因为immer的原因,我们必须直接操作state
const index = state.findIndex(item => item.id === action.payload);
state.splice(index, 1);
}
}
});
// 官方推荐使用 ES6 解构和导出语法
// 提取action creator 对象与reducer函数
const { reducer, actions } = todoSlice;
// 提取并导出根据reducers命名的 action creator 函数
export const { addTodo, delTodo } = actions;
// 导出 reducer 函数
export default reducer;
这里我们按照官方推荐使用 ES6 解构和导出语法,来获取 action creator 与 reducer。
从代码可以看出,通过 createSlice
函数,我们减少了绝大多数的模板代码,我们无需再写 type、action creator,这一切 RTK 都帮我们在背后默默实现了。
而且我们无需再关注状态不可变,我们只需像操作普通对象一样,在 reducers 中的 case 函数中随意操作对象,这一切都被 immer 帮我们搞定了。
你可能会好奇,最终通过 action creator 函数返回的是一个这样的 action 对象:
{type: 'todo/addTodo', payload: ...}
我们在 reducers 中写的 case 中拿到的 action 就是这样的固定格式,这一定需要注意,因为在我们过去使用 redux 时,并没有规范 action 对象的类型。
ps:可以看到 RTK 自动的帮我们将 type 命名为了 name + case函数名
这样的格式,这对于我们调试也是非常方便的,起名困难症表示 RTK 赛高!
**注意:**其实代码修改到这里,我们的程序就可以正常运行了,这也是 RTK 的一大优势,我们可以不必全部重新改造我们的程序,它没有增加我们的心智负担,并不需要我们重新去掌握一个库,而是通过这些工具性质的函数,帮助我们更好的使用 Redux。
- 使用
configureStore
函数创建 store 对象
import { configureStore } from "@reduxjs/toolkit"
import todoReducer from "./todo.slice";
export default configureStore({
reducer: {
todos: todoReducer
// ... 其他的 reducer
}
});
configureStore
函数接收一个配置redux的对象作为参数,具有如下选项:
-
reducer
必选参数,可以是一个 reducer 函数也可以是一个由 slice reducer 构成的对象如果是 reducer 函数,就直接将他作为根 reducer,如果是一个对象,就自动帮我们调用
combineReducers
函数来合并多个 reducer 创建根 reducer 函数 -
middleware
中间件 -
devTools
是否启用 Redux DevTools
更多配置,请查看文档:redux-toolkit.js.org/api/configu…
剩余的使用是与原来一模一样的,我们无需改动其他代码!
其他 api 介绍
除了我们上面介绍的这种比较完整的改造,其实 RTK 还提供了很多粒度更小的 api,来方便我们改造原有的模板代码,这里我们简单介绍几个:
1. createAction
这个函数可以帮我们减少创建 action 时的模板代码,还有我们的例子来说明:
// before
const ADD_TODO = 'ADD_TODO';
const DEL_TODO = 'DEL_TODO';
export const addTodo = (payload) => ({ type: ADD_TODO, payload });
export const delTodo = (payload) => ({ type: DEL_TODO, payload });
const action = addTodo({...});
// after
const addTodo = createAction('ADD_TODO')
const delTodo = createAction('DEL_TODO')
const action = addTodo({...});
我们完全不需要再写 action creator 函数了,直接使用 createAction
就可以帮我们创建,它的源码其实也很简单:
export function createAction(type: string, prepareAction?: Function): any {
// action creator 函数
function actionCreator(...args: any[]) {
if (prepareAction) {
let prepared = prepareAction(...args)
if (!prepared) {
throw new Error('prepareAction did not return an object')
}
return {
type,
payload: prepared.payload,
...('meta' in prepared && { meta: prepared.meta }),
...('error' in prepared && { error: prepared.error }),
}
}
//action creator 函数返回的固定样式的 action 对象
return { type, payload: args[0] }
}
// 自定义 action creator 函数的 toString
actionCreator.toString = () => `${type}`
actionCreator.type = type
actionCreator.match = (action: Action<unknown>): action is PayloadAction =>
action.type === type
return actionCreator
}
2.createReducer()
这个函数可以帮我们简化 reducer 函数的创建,并在其内部使用了 immer ,让我们可以直接操作 state,而不用再考虑不可变的问题,极大的简化了更新状态的逻辑。我们一般用 createAction
配合使用
// before
const initState = [];
//模板代码 action type 常量
const ADD_TODO = 'ADD_TODO';
const DEL_TODO = 'DEL_TODO';
//模板代码用于创建 action 的 action creator 函数
export const addTodo = (payload) => ({ type: ADD_TODO, payload });
export const delTodo = (payload) => ({ type: DEL_TODO, payload });
// reducer 函数
export default function todoReducer(preState = initState, action) {
const { type, payload } = action;
switch (type) {
case ADD_TODO:
return [...preState, payload];
case DEL_TODO:
return preState.filter(item => item.id !== payload);
default:
return preState;
}
}
//after
import { createReducer, createAction } from "@reduxjs/toolkit";
const initialState = [];
//使用 createAction 函数创建 action creator
export const addTodo = createAction('ADD_TODO')
export const delTodo = createAction('DEL_TODO')
//使用createReducer生成reducer函数
export default createReducer(initialState, {
[addTodo]: (state, action) => {
state.push(action.payload)
},
[delTodo]: (state, action) => {
const index = state.findIndex(item => item.id === action.payload);
state.splice(index, 1);
}
})
现在我们的代码变得更加简洁,几乎没有了模板代码,非常的优雅!
除了上面我演示的这种方式以外,RTK 还支持使用 Builder Callback
这种方式,大家要是感兴趣可以自行查看文档:redux-toolkit.js.org/api/createR…
好了,关于 RTK 的话题我们就介绍到这里了,更多有趣的内容请关注我的专栏!
转载自:https://juejin.cn/post/7127176987067547661