上篇文章如何使用Hooks和Context API取代Redux》中提到了使用hooks代替redux创建可共享的全局状态的例子,本文参考了一些工程案例的写法并结合个人的实际使用,对上篇文章的hooks的使用做了一些改良,希望能给大家提供不同的思路。

回顾

我们先来回顾一下上篇文章中的状态共享的大致写法:

  1. 创建一个contact-context.js

    1. // 定义默认值
    2. const initialState = {
    3. // 具体省略...
    4. }
    5. // 定义reducer
    6. const reducer = (state, action) => {
    7. // 具体省略...
    8. }
    9. // 返回一个provider
    10. export const ContactContextProvider = props => {
    11. const [state, dispatch] = useReducer(reducer, initialState);
    12. return (
    13. <ContactContext.Provider value={[state, dispatch]}>
    14. {props.children}
    15. </ContactContext.Provider>
    16. );
    17. };
  2. 容器组件引入Provider

    1. export default function Contacts() {
    2. return (<ContactContextProvider>
    3. ...
    4. </ContactContextProvider>)
    5. }
  3. 具体组件使用useContext引入状态

    import { ContactContext } from "../context/contact-context";
    export default function ContactTable() {
    // 获取状态 `state` 和改变状态的函数:dispatch
     const [state, dispatch] = useContext(ContactContext);
      ... 
    // state.xxx 获取具体状态的值
    // dispatch(action)改变状态的值
    }
    

    以上其实会遇到一些实际场景:
    1.某些组件里其实并不需要获取state的值,只会用到用于改变状态的dispatch函数(比如一些交互按钮的组件封装),而由于导出顺序和useContext写法的原因,我们只想用dispatch也必需要引入state。并且state改变之后组件也会重新渲染,就可能会引发不必要的重新计算。
    2.所有状态都需要通过state.xxx的方式获取,维护者必须需要通过查看contact-context才知道具体都定义了哪些state
    3.通常我们实际项目中会定义多组context,当一个组件中依赖了多组context,多个useContext写起来没有那么优雅。

    改进写法

  4. 创建一个context.js

    // 定义默认值
    const initialState = {
     state1: '',
    state2: {}
    // ...
    }
    // 定义reducer
    const reducer = (state, action) => {
     // 具体省略...
    }
    // 创建provider
    function XxxProvider({ children }) {
    var [state, dispatch] = React.useReducer(reducer, initialState)
     // 将state和dispatch拆分成两个Context
    return (
     <XxxStateContext.Provider value={state}>
       <XxxDispatchContext.Provider value={dispatch}>
         {children}
       </XxxDispatchContext.Provider>
     </XxxStateContext.Provider>
    )
    }
    // 提供state的hook useXxxState
    function useXxxState() {
    var context = React.useContext(XxxStateContext)
    if (context === undefined) {
     throw new Error(
       'useXxxState must be used within a XxxProvider'
     )
    }
    return context
    }
    // 提供dispatch的context
    function useXxxDispatch() {
    var context = React.useContext(ModuleTreeDispatchContext)
    if (context === undefined) {
     throw new Error(
       'useXxxDispatch must be used within a XxxProvider'
     )
    }
    return context
    }
    
  5. 容器组件引入Provider

这和原来的写法一样,不再重复描述

  1. 具体组件使用

只依赖state的组件

import { useXxxState } from 'path/to/context'
function (props) {
  const {state1, state2} = useXxxState()
  // ...
}

只依赖dispatch的组件

import { useXxxDispatch } from 'path/to/context'
function (props) {
  const dispatch = useXxxDispatch()
  // ... state改变不会引发组件的重新render
}

总结

今天介绍的这种改进写法,主要是提供了useXxxState和useXxxDispatch自定义hooks的封装,和原来的写法相比,代码更佳简洁,命名更佳明确;针对只依赖dispatch不依赖state的组件也会减少不必要的重新render;如果遇到一些需要依赖其他属性计算的state,也可以很好的在useXxxState中统一封装(不过原来的写法也可以在useReducer中进行封装)。具体大家觉得哪种写法更好,可以进一步实践验证一下。