如何实现一个redux-toolkit
-
前言
当我们使用纯redux的时候,我们用createStore创建store,会发现提示
react推荐我们使用"@reduxjs/toolkit"(以下简称rtk)。 实际上rtk是对redux的一层封装。在官方文档中,说"Redux Toolkit 简化了编写 Redux 逻辑和设置 store 的过程。"
我们先来看看rtk如何使用?
- 创建counterSlice
js
import {createSlice} from '../rtk-nut/createSlice'
const counterSlice = createSlice({
name:'counter',
initialState:{value:0},
reducers:{
increment:(state)=>{
state.value += 1
},
decrement:(state)=>{
state.value -= 1
},
incrementByAmount:(state,action)=>{
state.value += action.payload
}
}
})
export const {increment, decrement,incrementByAmount} = counterSlice.actions
export default counterSlice.reducer
那么它和redux中的countReducer有什么区别?
js
function countReducer(state = 0, action)
{
switch(action.type){
case "ADD":
return state + 1
case "Minus":
return state - 1
default:
return state
}
}
除了基本的用法之外,我们可以注意到修改状态的方式变了。redux中是返回一个新的状态,而rtk是对状态中的某个值进行修改。这里我们可以理解为(个人理解):redux是函数式的编程思想,rtk像是面向对象的思想。那么为什么可以这么做呢?这实际上是rtk内置了immer,他帮我们在背后进行了state的状态值修改。
设想对于一个复杂的状态,我们想要返回新的状态值会有很多不改变的,也许只有一个改变,那么我们可以很大程度上简化状态修改逻辑。
- 创建状态仓库
js
export default configureStore({
reducer:{
counter: countReducer
}
})
- 在页面中使用rtk
js
export default function RtkPage(){
const [state, setState] = useState(store.getState().counter.value);
const add = () => {
store.dispatch(increment())
}
const add100 = () => {
store.dispatch(incrementByAmount(100))
}
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState().counter.value);
});
return () => unsubscribe();
}, []);
return(
<div>
<h3>RtkPage</h3>
<p>{state}</p>
<button onClick={add}>add</button>
<button onClick={add100}>add100</button>
<button onClick={()=>store.dispatch({type:"counter/increment"})}>add</button>
</div>
)
}
注意 我们可以使用react-redux简化这里,具体看上一篇文章
接下来让我们看看怎么实现一个rtk
- configureStore
接收{reducer},创建状态仓库,return 一个 store。
还记得我们之前实现了redux中的createStore和combineReducers吗?juejin.cn/post/753167...
js
export function configureStore({reducer}){
const rootReducer = combineReducers(reducer)
const store = createStore(rootReducer)
return store
}
那么我们很容易就建立了一个状态仓库。
- createSlice
接收{name,initialState,reducers},返回{name,reducer,actions}
(1)name不用做任何修改,直接传出来就行
(2)actions 要注意 incrementByAmount:(state,action)=>{ state.value += action.payload }
这种用法,我们要获取type和payload(args[0])。
(3)reducers本身不麻烦,但是要注意rtk内置了immer,不是标准的reducer,我们目前可以先使用rtk的createReducer函数。
js
import {createReducer} from '@reduxjs/toolkit'
export function createSlice({name,initialState,reducers}){
const reducerNames = Object.keys(reducers)
const actionCreators = {}
const sliceCaseReducerByType = {}
reducerNames.forEach(reducerName=>{
const r = reducers[reducerName]
const type = `${name}/${reducerName}`
sliceCaseReducerByType[type] = r
actionCreators[reducerName] = createAction(type)
})
let _reducer
function buildReducer(){
return createReducer(initialState,(builder)=>{
for(let key in sliceCaseReducerByType)
builder.addCase(key,sliceCaseReducerByType[key])
})
}
return{
name,
actions:actionCreators,
reducer: (state,action)=>{
if(!_reducer){
_reducer = buildReducer()
}
return _reducer(state,action)
}
}
}
function createAction(type){ //把接收到的参数转化成payload,type给dispatch调用
function creator(...args){
return {
type,
payload: args[0]
}
}
creator.type = type
return creator
}
- createReducer
接收nitialState和一个Callback,返回一个reducer,这个步骤是把虚假的reducer转化为真正的reducer。immer中的produce函数就是把它变成真正的reducer。
js
import {produce} from "immer"
function createReducer(initialState,mapOrBuilderCallback){
let [actionMap] = executeReducerBuilderCallback(mapOrBuilderCallback)
// actionMap就是 counter/increment: reducer .....
function reducer(state = initialState,action){
const caseReducers = [actionMap[action.type]]
return caseReducers.reduce((previousState,caseReducer)=>{
if(caseReducer){
return produce(previousState,(draft)=>{
return caseReducer(draft,action)
})
}
return previousState
},state)
}
return reducer
}
function executeReducerBuilderCallback(mapOrBuilderCallback){
const actionsMap = {};
const builder = {
addCase: (type, reducer) => {
actionsMap[type] = reducer;
return builder;
},
};
mapOrBuilderCallback(builder);
return [actionsMap];
}
至此我们就写完了rtk。
待拓展:immer