【React 系列 02】❤️ Custom Hooks 中使用 React Context

在上一篇 对于 Custom React Hooks 一些思考 文章末尾提及了 React Context,那么在本篇中我们将了解一下 React Context 怎么解决状态共享问题以及一些其它想法。

【React 系列 02】❤️ Custom Hooks 中使用 React Context_第1张图片

关于 React Context

提供 官网对于 usecontext 的介绍

const value = useContext(MyContext);

无论组件在组件树中的深度如何,React Context 都为组件提供数据。 上下文用于管理全局数据,例如 全局状态、主题、服务、用户设置等。

那么我们回到上篇文章提及到的例子吧,如何在父子组件中共享 useBoolean 返回的状态呢?

【React 系列 02】❤️ Custom Hooks 中使用 React Context_第2张图片

怎样使用 Context

在给出一份解决方案前,先来说明一下如何使用 Context。步骤也是比较简单:

  • creating the context
  • providing the context
  • consuming the context

creating the context

首先在 src 目录下创建一个名为 context 的文件夹,创建 index.tsx 文件

通过 createContext 来进行创建操作

import {
      createContext } from 'react';

const GlobalContext = createContext({
     })

export default GlobalContext;

providing the context

接下来,在父组件中 providing the context,并且将之前编写的 Custom Hooks(useBoolean)返回的 state 作为 value 值提供给子组件。

import SubApp from './subApp';
import useBoolean from './custom-hooks/useBoolean/index';
import GlobalContext from './context/index'

function App() {
     
  const [state, {
      toggle, setTrue, setFalse }] = useBoolean(true);

  return (
    <div>
      <p>Effects:{
     JSON.stringify(state)}</p>
      <p>
        <button type="button" onClick={
     () => toggle()}>
          Toggle
        </button>
        <button type="button" onClick={
     setFalse} style={
     {
      margin: '0 16px' }}>
          Set false
        </button>
        <button type="button" onClick={
     setTrue}>
          Set true
        </button>
      </p>
      // providing the context
      <GlobalContext.Provider value={
     state}><SubApp /></GlobalContext.Provider>
    </div>
  )
}

export default App;

consuming the context

作为消费方法其实有两种,一种是通过 useContext(Context),另外一种是通过 Context.Consumer 提供一个特殊组件的渲染函数。

通过 useContext(Context)

子组件代码编写如下:

import {
      useContext } from 'react'
import GlobalContext from './context/index'

const SubApp = () => {
     
  // useContext
  const val = useContext(GlobalContext)
  return (
    <div>
      <p>subEffects:{
     JSON.stringify(val)}</p>
    </div>
  )
}
export default SubApp

看起来这种实现方式也是比较简单的,我们看看效果,当我们点击 Toggle 按钮时,发现在父子状态进行了共享,效果实现。

【React 系列 02】❤️ Custom Hooks 中使用 React Context_第3张图片

通过 Context.Consumer

接下里,试一下第二种方式,代码如下:

import GlobalContext from './context/index'

const SubApp = () => {
     
  return (
    <div>
      <GlobalContext.Consumer>
        {
     (val) => <p>subEffects:{
     JSON.stringify(val)}</p>}
      </GlobalContext.Consumer>
    </div>
  )
}
export default SubApp

进行相同的操作,也同样实现了效果。
【React 系列 02】❤️ Custom Hooks 中使用 React Context_第4张图片

拓展知识

经过上述两个方法的实现,基本上也解决了上篇文章提及的问题。在这里补充一下关于 context 的知识。

对于我们创建的 context,就比如上文我创建的 GlobalContext,它可以拥有任意数量的Consumer。

如果 GlobalContext 值发生变化(通过更改 Context.Provider 的 value 属性 ),那么所有的 Consumer 都会立即收到通知并重新渲染。

可能文字的表述不是很清楚,那么就以下列图示来说明(图画的有点简单,凑合下吧 hh)

【React 系列 02】❤️ Custom Hooks 中使用 React Context_第5张图片
假设组件 APP 是我们的 Provider,底下组件 ABCE 都是它的 Consumer,那么当我更改 Context.Provider 的 value 属性时,所有的 Consumer(这里指 ABCE)都会收到通知并重新渲染。

细心的小伙伴可能发现了,不还有个组件 D 没说嘛,下面就来说明:

如果 Consumer 没有被包装在 Context.Provider 中,但仍然尝试访问 Context (使用 useContext(Context) 或 ),那么 Context 值拿到的是 createContext(defaultValue) 的默认值 。

const GlobalContext = createContext({
     })

如上述代码,就比如之前创建的 GlobalContext,子组件如果被包装在 GlobalContext.Provider 中,那么获取的就是更改的 value 值,而如果没有被包的话,就是获取的默认值(这里是 {})

Context 该何时何地使用

在上文,我们了解了 Context 使用好处,但我们应该在哪个场景去使用它呢?难道不成就随便用就好了?会给我们带来什么问题呢?

我们一步一步来回答上述问题。

首先是我们应该在哪个场景去使用它呢?

一提到共享状态以及本文中创建的也是 GlobalContext,就想到全局状态了。

我们可以存放一些全局状态,比如一些用户基本信息,应用的一些配置项(比如认证,基本信息等),以及服务相关等等,这个就可以根据具体的业务需求来决定了。

但是,在我们使用 Context 不得不考虑一下该怎样使用它,难道真就随便用?

就以本文的代码例子来说,我们通过了两种方式在父子组件中实现状态的共享,但是发现没有,只要包括在 GlobalContext.Provider 其中的子组件,都必须使用 useContext(Context) 和 Context.Consumer 或者其它可能的方式来实现。

如果子组件很多,那这样层层环扣,岂不是影响的层级会比较多,而且在子组件中就会多增加一行代码,整个树结构复杂性就会上升。

当然,如果子树必须共享状态或者组件内有大量的计算,那么使用 Context 还是会方便许多,它可以减少一些没必要的计算,直接共享就完事了。

结尾

那么,本文到此就结束了,你会期待接下来的文章吗?

我是【一百个Chocolate】,希望在文章中体现自己的思考,然后与大家分享,坚持学习打卡第二篇,我们下期再见。

你可能感兴趣的