阅读视图

发现新文章,点击刷新页面。

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中相应某个值。
这里扁平化逻辑实则对reducerdispatch进行二次封装。

//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
           });
       }
   };
};

  1. 先说对dispatch进行二次封装,useContext.tsx里面 我们通过将dispatch传入来派发数据 同时返回一个对象,然后在Provider将对象解构,也就是useContext.tsx文件里面的15 、21行这样我们在全局就注入了setXXX函数
  2. 对于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届自己菜,感觉不适合这一行,但又不知道干啥,就只能这样了)

❌