useReducer+useContext +扁平化
背景
作者之前是在百度实习,从事的是小程序和h5的开发,虽然H5用的hooks,但是对于某些hooks的用法不是很熟悉,对于大型Saas系统状态管理不是很熟悉,因此写下这篇来回顾总结上次遇到知识点不足的问题
useReducer
useReducer
接收三个参数:
- reducer 函数:指定如何更新状态的还原函数,它必须是
纯函数
,以 state 和 dispatch 为参数,并返回
下一个状态。 - 初始状态:初始状态的计算值。
- (可选的)初始化参数:用于返回初始状态。如果未指定,初始状态将设置为 initialArg;如果有指定,初始状态将被设置为调用
init(initialArg)
的结果。
useReducer
返回两个参数:
- 当前的状态:当前状态。在第一次渲染时,它会被设置为
init(initialArg)
或 initialArg(如果没有 init 的情况下)。 - dispatch:调度函数,用于调用 reducer 函数,以更新状态并触发重新渲染。
useContext
使用createContext
创建上下文
export const ContextDemo = createContext(initDefault);
并且在外层使用Context.Provider来包裹子组件,使用的context 则是
const {state, setSku} = useContext(ContextDemo);
扁平化
我们扁平化的目的就是在于,我们写代码更改某个值,希望使用的函数是类似于setXXX, 然而对于reducer来讲就是需要dispatch
,但是这样在页面写dipspatch,有可能会充斥大量dispatch xxx类似这样的代码,影响阅读,同时调用多个相同的dispatch会增加代码行数,因此需要扁平化。使得我们context里面有state以及更改state中相应某个值。
这里扁平化逻辑实则对reducer
和dispatch
进行二次封装。
//useContext.tsx
import React, {createContext, FC, ReactNode, useReducer} from 'react';
import {dispatcher, initState, reducer as useReducers} from './useReducer';
import {Action} from './interface';
const reducer = (State, action: Action) => {
// 这里更改数据对数据进行二次封装和更改,应该可以算作策略设计模式吗?
const fn = useReducers[action.type];
return fn(State, action.value);
};
export const ContextDemo = createContext({});
export const Provider: FC<{children: ReactNode}> = ({children}) => {
const [contextValue, dispatch] = useReducer(reducer, initState);
// 这里传入dispatch,对dispatch进行二次封装
const setter = dispatcher(contextValue, dispatch);
return (
<ContextDemo.Provider
value={{
state: contextValue,
...setter
}}
>
{children}
</ContextDemo.Provider>
);
};
在上面代码中,我们关注到在useReducer
的第一个参数中对数据进行二次封装抽象,这样避免了很多switch case等等
同时我们在Provider(14行
)对dispatch进行二次封装
这两个封装是针对同一个useReducer的,因此我将他们写在useReducer.tsx
文件里面
import {State, Action} from './interface';
export const initState: State = {
sku: '',
image: '',
dockerUrl: ''
};
const setter = (s: any, props: string, value: any) => {
return {
...s,
[props]: value
};
};
type action = 'setSku' | 'setImage' | 'setImageUrl';
type Reducer = (s: State, value?: any) => State;
export const reducer: {[key in action]: Reducer} = {
setSku: (state: any, val: any) => setter(state, 'sku', val),
setImage: (state: any, val: any) => setter(state, 'image', val),
setImageUrl: (state: any, val: any) => setter(state, 'imageUrl', val)
};
// 外部传入 dispatch 和 state ,在这里二次封装
export const dispatcher = (state: State, dispatch: (val: {type: action; value?: string}) => void) => {
return {
setSku(value: string) {
dispatch({
type: 'setSku',
value: value
});
// 这里可以return 做些其他逻辑
},
setImage(value: string) {
dispatch({
type: 'setImage',
value: value
});
},
setImageUrl(value: string) {
dispatch({
type: 'setImageUrl',
value: value
});
}
};
};
- 先说对dispatch进行二次封装,
useContext.tsx
里面 我们通过将dispatch
传入来派发数据 同时返回一个对象,然后在Provider将对象解构,也就是useContext.tsx文件里面的15 、21行
这样我们在全局就注入了setXXX
函数 - 对于State的修改我们要记住,reducer必须返回新的state 不然不会更新数据,既然扁平化,我在
useReducer
里面 写了reducer对象
(不是函数目的在于映射),代码useContext.tsx使用的reudcer是第五
行的reducer函数
,在reducer函数里面接收到action和state(注意reducer as useReducers} from './useReducer';
作者能力有限,命名不是很规范)通过action的type,映射到useReducer.tsx
的reducer对象的更改某个值的函数,这样封装完成了对数据的修改和返回
总结
这样扁平化之后我们context上下文就是state以及各种setXXX的函数,useContext与useReducer的结合用法在掘金有很多文章,作者只不过进一步扁平化罢了, 实际场景中函数有副作用,比如获取数据,结合hooks的方案,常用的就是使用react-query
这个库,还有很多状态管理库 比如zustand
redux
等,道阻且长。点个赞吧(作者25届自己菜,感觉不适合这一行,但又不知道干啥,就只能这样了)