如何使用和实施用于反应状态管理的 redux 工具包。
上一篇文章react-redux 使用与实现原理介绍了在react
中使用react-redux
将redux
中的store
注入到组件中从而自动地根据store
变化render
组件, 但是仍然没有解决注册action type
与action creator
比较麻烦,reducer
函数庞大复杂等问题。
本篇文章将介绍redux
官方推荐的redux toolkit
工具集,使用redux toolkit
可以配置 store
、定义 reducer
,更方便的构建不可变数据state
、方便地创建整个状态的切片 slice
,无需手动编写任何 action creator
或者 action type
什么是 redux-toolkit
redux-toolkit 是官方的, 开箱即用的且强大的用于高效开发应用的redux
工具集,它有如下几个特点:
- 使用
configureStore
创建redux store
简单高效, 减少很多模板代码 - 使用
createReducer
将action type
映射到reducer
函数,而不是编写switch...case
语句 - 可以添加任意中间件,内置
redux-thunk
中间件, 默认开启 redux-dev-tools - 集成
immer
, 在reducer
中创建不可变数据将变得非常简单 - 使用
createSelector
创建可记忆的selector
函数, 优化性能
使用
以下面的例子为例我们将实现一个简单的计数器以及一个点击add todo
按钮在列表中增加一项的功能,效果如下:
store.ts:
import { configureStore, createSlice } from '@reduxjs/toolkit';
interface CounterState {
value: number
}
const counterInitState: CounterState = {
value: 0,
}
const counterSlice = createSlice({
name: 'counter',
initialState: counterInitState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
}
}
});
interface TodoState {
todos: string[]
}
const todoInitState: TodoState = {
todos: []
}
const todoSlice = createSlice({
name: 'todo',
initialState: todoInitState,
reducers: {
addTodo: (state) => {
state.todos = ['Use Redux']
}
}
})
export const counterActions = counterSlice.actions
export const todoActions = todoSlice.actions
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
todo: todoSlice.reducer,
},
})
App.tsx:
import React from 'react';
import logo from './logo.svg';
import styles from './App.module.css';
import { useDispatch, useSelector } from 'react-redux';
import { todoActions, counterActions } from './store';
function App() {
const dispatch = useDispatch();
const state: any = useSelector((state: any) => state)
const handleDecrement = () => {
dispatch(counterActions.decrement())
}
const handleIncrement = () => {
dispatch(counterActions.increment())
}
const handleAddTodo = () => {
dispatch(todoActions.addTodo())
}
return (
<div className={styles.App}>
<header className={styles['App-header']}>
<img src={logo} className={styles['App-logo']} alt="logo" />
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Decrement value"
onClick={handleDecrement}
>
-
</button>
<span className={styles.value}>{state.counter.value}</span>
<button
className={styles.button}
aria-label="Increment value"
onClick={handleIncrement}
>
+
</button>
</div>
<div className={styles.row}>
<ul>
todos:
{
state.todo.todos.map((item: any) => (<li key={item}>{item}</li>))
}
</ul>
<button
className={styles.button}
aria-label="Add todo"
onClick={handleAddTodo}
>
add todo
</button>
</div>
</div>
</header>
</div>
);
}
export default App;
可以看到在上面的代码中通过 createSlice
函数创建了两个状态切片 counterSlice
和 todoSlice
, 并通过 configureStore
函数创建了 store
。这样便可以通过 counterSlice
和 todoSlice
获取对应的 action
,并且在reducer
中可以直接操作state
,内置的 immer
库,会帮我们构建一个不可变数据state
并返回。
实现
configureStore
首先来看 configureStore
, 实际上是在 configureStore
中调用了 redux
的 combineReducers
来组合各个 reducer
然后再返回 store
import { combineReducers, createStore } from 'redux';
function isPlainObject(value: any) {
if (typeof value !== "object" || value === null) return false;
return Object.getPrototypeOf(value) === Object.prototype;
}
function configureStore(options: any) {
let { reducer } = options;
let rootReducer;
if (typeof reducer === "function") {
rootReducer = reducer;
} else if (isPlainObject(reducer)) {
rootReducer = combineReducers(reducer);
}
return createStore(rootReducer);
}
export default configureStore;
createSlice
接下来实现 redux toolkit
中十分重要的 createSlice
函数的实现,createSlice
中用到了 createAction
函数,createAction
函数用于创建 action creator
给 dispatch
函数派发,先来实现 createAction
createAction
接收一个 type
, 返回一个 actionCreator
, 该 actionCreator
返回一个 action
, action.type
即为传入的 type
参数
function createAction(type: string) {
function actionCreator(...args: any) {
return {
type: type,
payload: args[0]
};
}
actionCreator.toString = function () {
return "" + type;
}
actionCreator.type = type;
return actionCreator;
}
export default createAction;
此外 createSlice
函数也用到了 createReducer
函数, 它接收一个初始 state
, 和一个 reducers
对象, reducers
对象的 key
即为 action.type
, value
即为真正的 reducer
; createReducer
使得用户可以用对象方式来配置 reducer
, 而不是用一个内部使用 switch...case
的函数来定义 reducer
function createReducer(initialState: any, reducers = {} as any) {
return function (state = initialState, action: { type: string }) {
let reducer = reducers[action.type];
if (reducer) return reducer(state, action);
return state;
}
}
export default createReducer;
createSlice
实际上就是集成了 createReducer
和 createAction
, 返回一个 reducer
函数和 actions
对象
import { createReducer, createAction } from './'
function createSlice(options: {
name: string;
initialState: any;
reducers: Record<string, Function>;
}) {
let { name, initialState = {}, reducers = {} } = options;
let actions: any = {};
const prefixReducers: any = {};
Object.keys(reducers).forEach((key) => {
const type = getType(name, key);
actions[key] = createAction(type);
prefixReducers[type] = reducers[key];
})
let reducer = createReducer(initialState, prefixReducers);
return {
name,
reducer,
actions
};
}
function getType(slice: string, actionKey: string) {
return slice + "/" + actionKey;
}
export default createSlice;
使用 redux toolkit
能极大地方便用户使用,做到开箱即用,内置的很多诸如 immer
, reselect
, redux-thunk
等库能帮助用户快速开发应用,可以说是现在使用 redux
的最佳实践了。
曾经
dva
作为redux
的工具集也流行过一段时间,但是redux toolkit
在使用上比dva
方便很多, 而且redux toolkit
内部也内置了本文没有介绍的RTK Query
,RTK Query
是一个强大的数据获取和缓存工具。不过dva
内置的副作用处理中间件是redux saga
而redux toolkit
内置的是redux thunk
, 下一篇文章redux middleware 的使用与实现原理我将介绍redux
的中间件的使用及其实现原理。
上一篇: 单细胞工具包|细胞游侠-V6.0 开始单细胞之旅(上)
下一篇: 孤獨的遐想