在上一篇 对于 Custom React Hooks 一些思考 文章末尾提及了 React Context,那么在本篇中我们将了解一下 React Context 怎么解决状态共享问题以及一些其它想法。
关于 React Context
提供 官网对于 usecontext 的介绍
const value = useContext(MyContext);
无论组件在组件树中的深度如何,React Context 都为组件提供数据。 上下文用于管理全局数据,例如 全局状态、主题、服务、用户设置等。
那么我们回到上篇文章提及到的例子吧,如何在父子组件中共享 useBoolean 返回的状态呢?
怎样使用 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
按钮时,发现在父子状态进行了共享,效果实现。
通过 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
进行相同的操作,也同样实现了效果。
拓展知识
经过上述两个方法的实现,基本上也解决了上篇文章提及的问题。在这里补充一下关于 context 的知识。
对于我们创建的 context,就比如上文我创建的 GlobalContext,它可以拥有任意数量的Consumer。
如果 GlobalContext 值发生变化(通过更改 Context.Provider 的 value 属性 ),那么所有的 Consumer 都会立即收到通知并重新渲染。
可能文字的表述不是很清楚,那么就以下列图示来说明(图画的有点简单,凑合下吧 hh)
假设组件 APP 是我们的 Provider,底下组件 ABCE 都是它的 Consumer,那么当我更改 Context.Provider 的 value 属性时,所有的 Consumer(这里指 ABCE)都会收到通知并重新渲染。
细心的小伙伴可能发现了,不还有个组件 D 没说嘛,下面就来说明:
如果 Consumer 没有被包装在 Context.Provider 中,但仍然尝试访问 Context (使用 useContext(Context) 或 <Context.Consumer>),那么 Context 值拿到的是 createContext(defaultValue) 的默认值 。
const GlobalContext = createContext({})
如上述代码,就比如之前创建的 GlobalContext,子组件如果被包装在 GlobalContext.Provider 中,那么获取的就是更改的 value 值,而如果没有被包的话,就是获取的默认值(这里是 {})
Context 该何时何地使用
在上文,我们了解了 Context 使用好处,但我们应该在哪个场景去使用它呢?难道不成就随便用就好了?会给我们带来什么问题呢?
我们一步一步来回答上述问题。
首先是我们应该在哪个场景去使用它呢?
一提到共享状态以及本文中创建的也是 GlobalContext,就想到全局状态了。
我们可以存放一些全局状态,比如一些用户基本信息,应用的一些配置项(比如认证,基本信息等),以及服务相关等等,这个就可以根据具体的业务需求来决定了。
但是,在我们使用 Context 不得不考虑一下该怎样使用它,难道真就随便用?
就以本文的代码例子来说,我们通过了两种方式在父子组件中实现状态的共享,但是发现没有,只要包括在 GlobalContext.Provider 其中的子组件,都必须使用 useContext(Context) 和 Context.Consumer 或者其它可能的方式来实现。
如果子组件很多,那这样层层环扣,岂不是影响的层级会比较多,而且在子组件中就会多增加一行代码,整个树结构复杂性就会上升。
当然,如果子树必须共享状态或者组件内有大量的计算,那么使用 Context 还是会方便许多,它可以减少一些没必要的计算,直接共享就完事了。
结尾
那么,本文到此就结束了,你会期待接下来的文章吗?
我是【一百个Chocolate】,希望在文章中体现自己的思考,然后与大家分享,坚持学习打卡第二篇,我们下期再见。