还在用Redux? [一文看懂] useContext + useReducer如何使用

2,177 阅读2分钟

前言

userReducer可以帮助我们集中式的处理复杂的state管理。但如果我们的页面是多层组件组成,我们如何在子组件中触发state变化呢?此时useContext就上场了。

例子

此时我们需要做如下几件事情:

  1. 创建globalState.jsx文件用来存放全局state,并初始化provider
  2. app.jsx文件,也就是入口文件处引入provider
  3. 在子组件中使用useContext

globalState.jsx文件

// globalState.jsx

// 创建默认状态值,store
const initState = {
    title: '',
    isShow: false,
}

// 定义action,作为dispatch的参数
const actionType = {
    SET_TITLE: 'SET_TITLE',
    SET_SHOW: 'SET_SHOW',
}

// 处理dispatch推送过来的数据
function reducer (state, action) {
  switch (action.type) {
    case actionType.SET_TITLE:
      return { ...state, title: action.payload }
    case actionType.SET_SHOW:
      return { ...state, isShow: true }
    default:
      reutrn state
  }
}

// 创建context全局上下文
const GlobalContext = createContext({}) // 注:使用ts写时createContext需要传1到2个参数,可用{}代替,否则会报错
或
const GlobalContext = createContext({ state: initState, dispatch(action) => {} })


// 定义provider
function GlobalProvider(props) {
    // 使用useReducer来初始化全局state
    const [state, dispatch] = useReducer(reducer, initState)
    
    return (
      <GlobalContext.Provider value={{ state, dispatch }}>
        {props.children} // 接收传入的值
      </GlobalContext.Provider>
    )
}

// 将context相关的要用到的内容导出
export {
    GlobalProvider,
    GlobalContext,
    initState,
    actionType,
    reducer,
}

tscreateContext的写法:

参考文档: stackoverflow.com/questions/5…

interface IContextProps {
  state: IState;
  dispatch: ({type}:{type:string}) => void;
}
 const AdminStore = React.createContext({} as IContextProps);

// ts写法 
interface StoreAction {
   type: string
   payload?: any
}
const GlobalContext = createContext({
  state: defaultData,
  dispatch: (action: StoreAction) => {},
})
// 并关闭 .eslintrc 规则
rules: {
  '@typescript-eslint/no-empty-function': 'off',
},

app.jsx入口文件

// 引入Store
import { GlobalProvider } from 'xxxxxx'
export function Root() {
    return (
      <GlobalProvider>
        // 子组件
        <ChildComponent /> 
      </GlobalProvider>
    )
}

在ChildComponent中使用useContext

// ChildComponent.tsx
import { actionType, GlobalContext } from 'xxxxxx'
function ChildComponent() {
  const { state, dispatch } = useContext(GlobalContext)
  
  useEffect(() => {
    dispatch({ type: actionType.SET_SHOW })
  },[])
  
  return (
    { state.isShow ? <div>{state.name}<div> : <Fragment /> }
  )
}

从上述例子中可以看出用useReducer+useContext,可以通过contextdispatch函数提供给组件树中的所有组件使用,而不用通过props的方式一层层传递。

使用Context比回调函数的优势:

  1. 对比回调函数的自定义命名,ContextAPI更加明确,可以清晰的知道哪些组件使用了dispatch、应用中的数据流动和变化。这也是React单向数据流的优势。
  2. 更好的性能。如果使用回调函数作为参数传递的话,因为每次render函数都会变化,也会导致子组件的rerender。当然,我们也可以使用useCallback来解决这个问题,但相比useCallbackReact官方更推荐使用useReducer,因为React会保证dispatch始终是不变的,不会引起consumer组件的rerender

总结:

  1. 如果页面的state很简单,建议直接使用useState
  2. 如果页面state比较复杂(state是一个对象或者state非常多散落在各处),建议使用useReducer
  3. 如果页面的组件层级比较深,且子组件需要出发state的变化,可以考虑useReducer+useContext相结合的方式来使用。