如果对redux还不熟悉的话,建议 可以去看我之前写过的一篇文章redux和react-redux从实现到理解。
前言
当我们使用redux
作为状态管理工具时,通常要书写很多模板代码,并且为了满足业务需求,还需要自己引入一些middleware
,整个过程是比较麻烦的。比如下面计数器管理的一个例子
import { createStore, combineReducers } from 'redux'
// actionTypes
const INCREMENT = 'counter/incremented';
const DECREMENT = 'counter/decremented';
// actions
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: INCREMENT });
// reducer
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { value: state.value + 1 };
case DECREMENT:
return { value: state.value - 1 };
default:
return state;
}
}
const store = createStore(
combineReducers({
counter: counterReducer,
// 其他reducer
}),
// 配置中间件一大推
applyMiddleware([a, b, c])
);
// 更改状态
store.dispatch(increment());
store.dispatch(decrement());
当我们业务变得复杂时,我们就需要将上述代码拆分成多个reducer
、actionTypes
、actions
,这样的模板代码书写不仅消耗着我们精力,而且分散多个文件,也不直观。
因此,redux-toolkit
尝试提供一些在配置过程中所需要的工具,并处理最常见的一些用例。它提供的工具使我们更方便的配置store,以及管理状态。下面来一起看看是如何使用。
基本使用
还是以计数器为例,通过redux-toolkit
提供的createSlice
、configureStore
来配置。
// 把actionType、actions、reducer放在一起管理
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
// 内置了immer,可以直接修改原state
incremented: state => {
state.value += 1;
},
decremented: state => {
state.value -= 1;
},
},
});
// 定义store,其中内置了thunk中间件
const store = configureStore({
reducer: {
[counterSlice.name]: counterSlice.reducer,
},
});
const { incremented, decremented } = counterSlice.actions;
// 更改状态
store.dispatch(increment());
store.dispatch(decrement());
上述代码相对于使用redux
API整体更简洁,并且我们可以用一个slice.js
文件来维护一个页面的状态管理,不需要多个文件去修改。
下面我们一起学习下它几个API的原理。😆
源码浅析
createAction
createAction
源码比较简单,就是根据我们传入的type
。去生成{ type, payload: xxx }
形式。如果传入了第二个参数,就需要进一步生成payload。
function createAction(type, prepareAction) {
// 返回的action
function actionCreator(...args) {
// 如果存在第二个参数,需要进一步生成payload。
if (prepareAction) {
let prepared = prepareAction(...args)
if (!prepared) {
throw new Error('prepareAction did not return an object')
}
return {
type,
payload: prepared.payload,
}
}
// 不存在第二个参数直接构造层action的格式
return { type, payload: args[0] }
}
// String()、console.log 时,可以返回type
actionCreator.toString = () => `${type}`
actionCreator.type = type
// 调用match函数是否匹配,type相等
actionCreator.match = (action) => action.type === type
return actionCreator
}
createReducer
先来看一下createReducer
的基本使用,第一个参数传入initState初始值,第二个参数如果是函数,会接受builder
参数,调用builder
的addCase
、addMatcher
方法可以添加case,如果是对象,就像reducer正常定义type以及对应的函数。
const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const initialState = { value: 0 }
const counterReducer1 = createReducer(initialState, (builder) => {
builder
.addCase(increment, (state) => {
state.value++
})
.addCase(decrement, (state) => {
state.value--
})
.addMatcher(() => true, (state, action) => {}) // 通过函数决定是否执行
.addDefaultCase((state, action) => {
// 默认case noting
})
})
const counterReducer2 = createReducer(initialState, {
[increment]: (state) => state.value++,
[decrement]: (state) => state.value--,
}, [
// 通过函数决定是否执行
{
matcher: () => true,
reducer(state, action) {},
},
],
(state) => {
// 默认case noting
})
// counterReducer2 和 counterReducer1 等价
可以看到第2个参数选择传入函数,利用builder
等同于2、3、4参数传入对象。
export function createReducer(initialState, mapOrBuilderCallback, actionMatchers = [], defaultCaseReducer) {
// 规范化参数,将builder函数形式,转变成3个参数
let [actionsMap, finalActionMatchers, finalDefaultCaseReducer] =
typeof mapOrBuilderCallback === 'function'
? executeReducerBuilderCallback(mapOrBuilderCallback)
: [mapOrBuilderCallback, actionMatchers, defaultCaseReducer]
// 确保获取初始状态是冻结的,是被immer包裹的
let getInitialState;
if (isStateFunction(initialState)) {
getInitialState = () => createNextState(initialState(), () => {})
} else {
const frozenInitialState = createNextState(initialState, () => {})
getInitialState = () => frozenInitialState
}
// 生成最后的reducer
function reducer(state = getInitialState(), action) {
// 所有符合条件的reducer
let caseReducers = [
actionsMap[action.type],
...finalActionMatchers
.filter(({ matcher }) => matcher(action))
.map(({ reducer }) => reducer),
]
// 如果没有符合的,就是用默认的case
if (caseReducers.filter((cr) => !!cr).length === 0) {
caseReducers = [finalDefaultCaseReducer]
}
// 执行所有符合条件的reducer
return caseReducers.reduce((previousState, caseReducer) => {
if (caseReducer) {
// 如果当前值是已经被immer代理过,就直接执行函数
if (isDraft(previousState)) {
const draft = previousState;
const result = caseReducer(draft, action)
// 如果结果是undefined,还是使用源数据就可以
if (typeof result === 'undefined') {
return previousState
}
// 如果有返回值,使用新值
return result
} else if (!isDraftable(previousState)) {
// 如果当前值无法被immer代理,需要判断是否会返回空值
const result = caseReducer(previousState, action)
// 如果之前就是空值,再返回空值是允许的
if (typeof result === 'undefined') {
if (previousState === null) {
return previousState
}
throw Error(
'A case reducer on a non-draftable value must not return undefined'
)
}
return result
} else {
// 使用immer再次代理,并传入reducer,获得下次结果
return createNextState(previousState, (draft) => {
return caseReducer(draft, action)
})
}
}
return previousState
}, state)
}
reducer.getInitialState = getInitialState
return reducer;
}
createReducer
的过程就是先规范化参数,然后找到所有符合当前type
的reducer
函数。依次执行Ta们。返回最终结果。在每次执行函数时,都会尝试用immer
代理结果值,使结果变成不可变性。
createSlice
通过createSlice
函数可以直接生成actions
、reducer
。actions
可以用于变更状态dispatch
参数,reducer
直接传入到configStore
函数中生成store
。
其实createSlice
就是使用createReducer
和createAction
生成一个对象。具体代码解析如下:
// options里面有 name、reducer、initialState
function createSlice(options) {
const { name } = options
if (!name) {
throw new Error('`name` is a required option for createSlice')
}
// 初始值使用immer代理,不可变性
const initialState =
typeof options.initialState == 'function'
? options.initialState
: createNextState(options.initialState, () => {})
const reducers = options.reducers || {}
const reducerNames = Object.keys(reducers)
// name -> reducer
const sliceCaseReducersByName = {}
// type -> reducer
const sliceCaseReducersByType = {}
// 生成action的Map
const actionCreators = {}
reducerNames.forEach((reducerName) => {
const maybeReducerWithPrepare = reducers[reducerName]
// type 是通过 name和reducer的key拼接成的。类似于counter/increment
const type = getType(name, reducerName)
let caseReducer
let prepareCallback
// 如果是对象且包含reducer,那结构是 { reducer, prepare }
if ('reducer' in maybeReducerWithPrepare) {
caseReducer = maybeReducerWithPrepare.reducer
prepareCallback = maybeReducerWithPrepare.prepare
} else {
caseReducer = maybeReducerWithPrepare
}
sliceCaseReducersByName[reducerName] = caseReducer
sliceCaseReducersByType[type] = caseReducer
// 如果有prepareCallback,则传入,会封装payload。具体可以看ceateActions逻辑
actionCreators[reducerName] = prepareCallback
? createAction(type, prepareCallback)
: createAction(type)
})
// 生成reducer
function buildReducer() {
const [
extraReducers = {},
actionMatchers = [],
defaultCaseReducer = undefined,
] =
typeof options.extraReducers === 'function'
? executeReducerBuilderCallback(options.extraReducers)
: [options.extraReducers]
// 可以通过extraReducers参数,传入额外的reducer、actionMatcher、默认case
const finalCaseReducers = { ...extraReducers, ...sliceCaseReducersByType }
// 具体逻辑可以看createReducer逻辑
return createReducer(
initialState,
finalCaseReducers,
actionMatchers,
defaultCaseReducer
)
}
let _reducer;
return {
name,
// 可以传入configStore
reducer(state, action) {
if (!_reducer) _reducer = buildReducer()
return _reducer(state, action)
},
// 用export导出,dispatch使用
actions: actionCreators,
caseReducers: sliceCaseReducersByName,
// 获取初始值
getInitialState() {
if (!_reducer) _reducer = buildReducer()
return _reducer.getInitialState()
},
}
}
configStore
configureStore(options) {
// 默认内置的middleware
const curriedGetDefaultMiddleware = curryGetDefaultMiddleware()
const {
reducer = undefined,
middleware = curriedGetDefaultMiddleware(),
devTools = true,
preloadedState = undefined,
enhancers = undefined,
} = options || {}
let rootReducer
// 生成reducer
if (typeof reducer === 'function') {
rootReducer = reducer
} else if (isPlainObject(reducer)) {
rootReducer = combineReducers(reducer)
}
// 生成middleware,可以传入函数,来改变默认中间件
let finalMiddleware = middleware
if (typeof finalMiddleware === 'function') {
finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware)
}
const middlewareEnhancer = applyMiddleware(...finalMiddleware)
let finalCompose = compose
// 有开发者工具,更换compose
if (devTools) {
finalCompose = composeWithDevTools({
// Enable capture of stack traces for dispatched Redux actions
trace: !IS_PRODUCTION,
...(typeof devTools === 'object' && devTools),
})
}
// 增加store
let storeEnhancers = [middlewareEnhancer]
if (Array.isArray(enhancers)) {
storeEnhancers = [middlewareEnhancer, ...enhancers]
} else if (typeof enhancers === 'function') {
storeEnhancers = enhancers(storeEnhancers)
}
const composedEnhancer = finalCompose(...storeEnhancers)
// 调用redux的createStore把整理好的参数传入
return createStore(rootReducer, preloadedState, composedEnhancer)
}
configStore
也很简单,就是将内置的middleware
、enhancer
和用户传入的组合一起,调用createStore
创建store
。