使用Context处理React全局共享数据

7,579 阅读3分钟

推荐在FC(function component)中使用,文中基本为FC组件

之前看到过使用Context去优雅的管理全局数据,这里做一些分享与代码组织方式,文中如果有不对的地方,欢迎大家指正出来!

用Context管理数据,更贴切React原生语法,并且新版ContextAPI性能也得以提升,之前旧版的Context发生改变时,并不能准确的知道具体有哪些子组件需要更新,需要所有组件更新(内部会有小优化去bailout跳过),新版ContextAPI更多像是一种依赖收集,将Context改变挂载在该组件中,就可以明确知道这个组件的依赖改变了,去更新他。

ReactFiber内Context依赖

(上图为ReactFiber内Context依赖)

Context使用回顾

使用Context实现全局主题

// index.jsx

const ThemeContext = React.createContext('light');

const Index = () => {
  const [theme, setTheme] = React.useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <App />
    </ThemeContext.Provider>
  )
}

在某一个具体button中使用

// Button.jsx

const Button = props => {
  // 可直接在该Button中应用主题
  const theme = React.useContext(ThemeContext)

  return (
    <button {...props} />
  )
}

这样就避免了组件中充满了无用的透传,使代码变得较为简洁。

通过Context修改全局数据

我们将状态缓存在最顶部,将state,与setState都传递下去即可,就达到了缓存在子组件内修改全局状态。

// CountStateContext.js
const CountStateContext = React.createContext();

// index.jsx
const Index = () => {
  const [countState, setCountState] = React.useState(0)

  return (
    <CountStateContext.Provider value={[countState, setCountState]}>
      <App />
    </CountStateContext.Provider>
  )
}

// Sub1.jsx
const Sub1 = () => {
  const [countState, setCountState] = React.useContext(CountStateContext)

  return (
    <>
      <h1>Count: {countState}</h1>
      <button onClick={() => {setCountState(c => c + 1)}}>Increment CountState</button>
    </>
  )
}

// Sub2.jsx
const Sub2 = () => {
    const [countState] = React.useContext(CountStateContext)

  return (
    <>
      <h1>Count: {countState}</h1>
    </>
  )
}

// Sub3.jsx
const Sub3 = () => {
    const [, setCountState] = React.useContext(CountStateContext)

  return (
    <>
      <button onClick={() => {setCountState(c => c + 1)}}>Increment CountState</button>
    </>
  )
}

Context都传递下去的问题

将一个顶层状态的statesetState都传递下去,但是不一定在所有的子组件中都会用到这两个数据

比如某个子组件中可能只是单纯想进行一个数据的修改,但是由于context中同时也依赖了state,所以当顶层组件的state发生改变时,该组件也会进行一次重新的渲染,那这样其实不符合我们的预期的,所以就将Context存储的数据拆分,那我们的核心就是读写分离!

可能会发生Context套娃,没关系先把主要问题解决了

// CountContext.js
const CountContext = React.createContext();
const SetCountContext = React.createContext();

// index.jsx
const Index = () => {
  const [count, setCount] = React.useState(0)

  return (
    <CountContext.Provider value={count}>
      <SetCountContext.Provider value={setCount}>
        <App />
      </SetCountContext.Provider>
    </CountContext.Provider>
  )
}

// Sub1.jsx
const Sub1 = () => {
  const count = React.useContext(CountContext)
  const setCount = React.useContext(SetCountContext)

  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={() => {setCount(c => c + 1)}}>Increment CountState</button>
    </>
  )
}

// Sub2.jsx
const Sub2 = () => {
  const count = React.useContext(CountContext)

  return (
    <>
      <h1>Count: {count}</h1>
    </>
  )
}

// Sub3.jsx
const Sub3 = () => {
  const setCount = React.useContext(SetCountContext)

  return (
    <>
      <button onClick={() => {setCount(c => c + 1)}}>Increment CountState</button>
    </>
  )
}

将Context存储的数据抽离出来

目前是将所有的数据都存储在顶层组件中的,我们也可以使用一个自定义Hooks去存储我们的数据,最终附带吐出一个带有Context的组件即可

// CountContext.jsx
const CountContext = React.createContext();
const SetCountContext = React.createContext();

function useCount() {
  return React.useContext(CountContext)
}

function useSetCount() {
  return React.useContext(SetCountContext)
}

function CountStoreProvider({ children }) {
  const [count, setCount] = React.useState()

  return (
    <CountContext.Provider value={count}>
      <SetCountContext.Provider value={setCount}>
        {children}
      </SetCountContext.Provider>
    </CountContext.Provider>
  )
}

export { useCount, useSetCount, CountStoreProvider }

// index.jsx
import { CountStoreProvider } from './CountContext'

const Index = () => {

  return (
    <CountStoreProvider>
      <App />
    </CountStoreProvider>
  )
}

// Sub1.jsx
import { useCount, useSetCount } from './CountContext'

const Sub1 = () => {
  const count = useCount()
  const setCount = useSetCount()

  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={() => {setCount(c => c + 1)}}>Increment CountState</button>
    </>
  )
}

最终我们创建了一个类似store的组件,即CountContext,之后便可以方便的修改我们的数据,将此Context内部的数据都封装在自定义Hooks内部,也减少了对外部代码的侵入,使用起来比较简洁。

配合Reducer使用

前面一直使用useState,那么将其存储为useReducer使用范围会更广,那么将Context中存储的数据修改为reducer会更加优雅一点,这一部分就留给大家去改造了。

在线实例

另外文中一直尝试的修改数据为同步的,如果有异步的数据修改,是将其在React组件内部处理好,再同步的dispatch出去,还是可以在context组件内部去做更多异步的处理,需要大家自己去实践了。

优雅!