useContext更佳实践

时间:2022-07-23
本文章向大家介绍useContext更佳实践,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

有人说:构建React应用就像用积木搭房子,每个组件就是一块积木。

这么说的人可能忽视了state(状态)—— 不同于组件是以树的形式组合,我们经常会在不同层级的组件间公用同一个state

面对这种情况,有些同学选择引入状态管理库(比如Redux)。

事实上,React内置的contextAPI可以解决大部分状态传递问题。

本文接下来要讲的,就是如何更有效的使用context

错误前置

在我们的Demo中,有个context——CountContext

当其render时如果上层结构中不存在context provider为他提供context value,则在解构context value时会报错。

const CountContext = React.createContext();

function CountDisplay() {
 // 解构语法报错
  const {count} = React.useContext(CountContext);
  return <div>{count}</div>;
}

这是因为CountContext没有默认值,所以为undefined。将undefined当作对象解构时报错。

在有些场景下默认值是有意义的。但是大多数情况,context consumer需要context provider为他提供有用的context value

这意味着使用context的业务组件需要判断undefined,否则可能出现运行时错误。

为了解决这个问题,我们可以用自定义hook错误前置

// src/count-context.js
const CountContext = React.createContext();

function useCount() {
  const context = React.useContext(CountContext);
  
  if (context === undefined) {
    throw new Error('必须在CountProvider内使用useCount');
  }
  
  return context;
}

同时提供CountProvider

// src/count-context.js

function CountProvider({children}) {
  const stateHook = React.useState();
  return (
    <CountContext.Provider value={stateHook}>
      {children}
    </CountStateContext.Provider>
  )
}

在需要CountContext的业务组件中,我们不再需要直接使用CountContext,而是引入useCountCountProvider

// src/app.jsx

import {useCount, CountProvider} from './count-context.js'

function CountDisplay() {
  const [count] = useCount();
  return <div>{count}</div>;
}

function App() {
  return (
    <CountProvider>
      <CountDisplay />
    </CountProvider>
  )
}

如果在未包裹CountProvider的情况下单独使用useCount,会直接抛出错误,而不需要等到使用context时再报错。

这种将错误前置的方式能够帮我们更好的规避运行时错误

state与dispatch分离

CountProvider中,stateHook作为context value传递给context consuer

function CountProvider({children}) {
  const stateHook = React.useState();
  // ...
}

其中state与改变state的方法(dispatch)同时存在于context value中。

有些时候,展示state的组件与触发state变化的组件不是同一个组件,比如:

function App() {
  return (
    <CountProvider>
      <CountDisplay />
      <Counter />
    </CountProvider>
  )
}

其中CountDisplay用于展示state

Counter用于触发state变化。

由于statedispatch同时存在于context valuestate变化后CountDisplayCounter都会重新render

这对于只负责触发组件state变化的Counter来说是不必要的。

为此,我们可以将statedispatch分离。

修改CountProvider

// src/count-context.js

const CountStateContext = React.createContext();
const CountDispatchContext = React.createContext();

function CountProvider({children}) {
  const [state, dispatch] = React.useState();
  return (
    <CountStateContext.Provider value={state}>
      <CountDispatchContext.Provider value={dispatch}>
        {children}
      </CountDispatchContext.Provider>  
    </CountStateContext.Provider>
  )
}

同时,useCount也拆分为useCountStateuseCountDispatch

// src/count-context.js

function useCountState() {
  const context = React.useContext(CountStateContext);
  
  if (context === undefined) {
    throw new Error('必须在CountProvider内使用useCountState');
  }
  
  return context;
}

function useCountDispatch() {
  const context = React.useContext(CountDispatchContext);
  
  if (context === undefined) {
    throw new Error('必须在CountProvider内使用useCountDispatch');
  }
  
  return context;
}

CountDisplay中使用useCountState

Counter中使用useCountDispatch

这样,即使state变化,只有CountDisplayrenderCounter不会render

更灵活的拓展

事实上,将context分离为动态(state),静态(dispatch)两部分后可以拥有更灵活的拓展。

当需要更多state时,可以将CountProvideruseState替换为useReducer

// src/count-context.js

function CountProvider({children}) {
  const [state, dispatch] = React.useReducer(countReducer);
  return (
    <CountStateContext.Provider value={state}>
      <CountDispatchContext.Provider value={dispatch}>
        {children}
      </CountDispatchContext.Provider>  
    </CountStateContext.Provider>
  )
}

当需要更多方法时,你也可以拓展CountDispatchContext

总结

通过这套context的“更”佳实践,审视下我们现有的业务,是不是很多时候并不需要额外的状态管理库呢?