多数情况下,在前端项目里引入状态管理库是为了在组件树内共享一些状态,避免层层透传的 props 降低代码的可维护性。
但对于简单的项目,通过 React Context 来共享状态就已经能满足需求了。
为了方便介绍,假设现在需要用 Context 在组件树内共享值 sharedValue 和逻辑 addOne,要求:
- 在 ComponentA 中展示 sharedValue 的值。
- 在 ComponentB 中展示一个按钮,按钮点击后会调用 addOne 加一。
熟悉 React 的人可能会这样做
假设你已经熟悉 React 了,很轻松地就能写出这样的代码:
// xxContext.ts
import { createContext } from 'react';
export const Context = createContext();
// RootComponent.tsx
import { Context } from './xxContext.ts';
import { useState } from 'react'
export const RootComponent = () => {
const [sharedValue, setSharedValue] = useState(1);
const addOne = () => {
setSharedValue(value => value + 1);
}
return <Context.Provider value={{ sharedValue, addOne }}>
<ComponentA />
<ComponentB />
</Context.Provider>
}
// ComponentA.tsx
import { Context } from './xxContext.ts';
import { useContext } from 'react';
export const ComponentA = () => {
const { sharedValue } = useContext(Context)
return <div>
当前 Context 中的值是 {sharedValue}
</div>
}
// ComponentB.tsx
import { Context } from './xxContext.ts';
import { useContext } from 'react';
export const ComponentB = () => {
const { addOne } = useContext(Context);
return <button onClick={addOne}>
点我 +1
</button>
};
但是这样做总感觉有点麻烦,因为在 ComponentA 和 ComponentB 中,我们既需要手动引入 Context,又需要通过 useContext 从 Context 上取值。
那能不能简化下呢?
把从 Context 上取值的逻辑抽成 hook
既然 React 引入 hook 的原因之一是为了逻辑复用,那我们就封装一个从 Context 上取值的 hook。
// xxContext.ts
import { createContext } from 'react';
export const Context = createContext();
export const useXXContext = () => useContext(Context); // ✅ 我们在 xxContext.ts 中新增这行代码
// RootComponent.tsx
import { Context } from './xxContext.ts';
import { useState } from 'react'
export const RootComponent = () => {
const [sharedValue, setSharedValue] = useState(1);
const addOne = () => {
setSharedValue(value => value + 1);
}
return <Context.Provider value={{ sharedValue, addOne }}>
<ComponentA />
<ComponentB />
</Context.Provider>
}
// ComponentA.tsx
import { useXXContext } from './xxContext.ts';
export const ComponentA = () => {
const { sharedValue } = useXXContext() // ✅ 优雅地从 xxContext 上取值
return <div>
当前 Context 中的值是 {sharedValue}
</div>
}
// ComponentB.tsx
import { useXXContext } from './xxContext.ts';
exort const ComponentB = () => {
const { addOne } = useXXContext();
return <button onClick={addOne}>
点我 +1
</button>
};
但现在如果我们需要共享其他新的状态,就需要修改 RootComponent 的代码了。
有办法解耦开吗?
把需要被共享的状态抽成 hook
我们可以将 RootComponent 中需要共享的状态抽成 hook,放到单独的文件维护。
因为这个 hook 用于创建被共享的状态,我们可以起名为 useCreateXXContext
// xxContext.ts
import { createContext } from 'react';
export const Context = createContext();
export const useXXContext = () => useContext(Context);
// useCreateXXContext.ts
import { useState } from 'react'
export const useCreateXXContext = () => { // ✅ 将被共享的状态抽成 hook
const [sharedValue, setSharedValue] = useState(1);
const addOne = () => {
setSharedValue(value => value + 1);
}
return {
addOne,
sharedValue
}
}
// RootComponent.tsx
import { Context } from './xxContext.ts';
import { useCreateXXContext } from './useCreateXXContext.ts';
export const RootComponent = () => {
const context = useCreateXXContext() // ✅ 创建需要被共享的状态,并传递给 Context
return <Context.Provider value={context}>
<ComponentA />
<ComponentB />
</Context.Provider>
}
// ComponentA.tsx
import { useXXContext } from './xxContext.ts';
export const ComponentA = () => {
const { sharedValue } = useXXContext()
return <div>
当前 Context 中的值是 {sharedValue}
</div>
}
// ComponentB.tsx
import { useXXContext } from './xxContext.ts';
exort const ComponentB = () => {
const { addOne } = useXXContext();
return <button onClick={addOne}>
点我 +1
</button>
};
这样写好看多了,但现在我们还是需要编写一些样板代码,比如我们需要创建 Context、创建需要被共享的状态、将需要被共享的状态传到 Context 里,我们还需要创建一个从 Context 上取值的 hook。
有办法消灭这些样板代码吗?
用工厂模式消灭样板代码
我们先看一下 RootComponent.tsx,有两个地方不够”通用”,分别是调用 useCreateXXContext 和 Context.Provider 的 children
import { Context } from './xxContext.ts';
import { useCreateXXContext } from './useCreateXXContext.ts';
export const RootComponent = () => {
const context = useCreateXXContext()
return <Context.Provider value={context}>
<ComponentA />
<ComponentB />
</Context.Provider>
}
再来看一下 xxContext.tsx,这个文件内做的事情也很简单,也就是创建了一个 context,创建了一个从 context 上取值的 hook。
// xxContext.ts
import { createContext } from 'react';
export const Context = createContext();
export const useXXContext = () => useContext(Context);
那我们可以借助工厂模式:
- 提取通用的部分:即创建 context、创建从 context 上取值的逻辑、将需要被共享的状态提供给 context
- 通过参数抹平差异:即 RootComponent 所包裹的 children、业务需要共享的状态(在这里是 useCreateXXContext )
// create.ts
import { createContext, useContext } from 'react';
export const createModel = (useCreateContext) => { // ✅ useCreateContext 这个 hook 存放在了闭包中,只有在 Provider 在被渲染时,hook 才会真正的执行
const context = createContext();
const _useContext = () => useContext(context);
const Provider = (props)=> { // ✅ 该组件的作用是将需要被共享的数据 提供 给子树
const initialContext = useCreateContext()
return <context.Provider value={initialContext}>
{props.children}
</context.Provider>
}
return {
Provider,
useContext: _useContext
}
}
来试试你刚刚亲手封装好的“状态管理库”
至此,我们就基于 React Context 封装了一个最简单的状态管理库。它的 API 也非常简单,使用起来如下:
// xxModel.ts
const useCreateXXContext = () => { // ✅ 这里仍然是业务自定义的、需要被共享的状态
const [sharedValue, setSharedValue] = useState(1);
const addOne = () => {
setSharedValue(value => value + 1);
}
return {
addOne,
sharedValue
}
}
export default createModel(useCreateXXContext) // ✅ 将它传入给我们封装好的 createModel 方法
// RootComponent.tsx
import { Provider } from './xxModel.ts';
export const RootComponent = () => {
// ✅ 用 Provider 包裹需要被共享状态的子树
return <Provider>
<ComponentA />
<ComponentB />
</Provider>
}
// ComponentA.tsx
import { useContext } from './xxModel.ts';
export const ComponentA = () => {
const { sharedValue } = useContext() // ✅ 直接从 Model 上引入共享的状态
return <div>
当前 Context 中的值是 {sharedValue}
</div>
}
// ComponentB.tsx
import { useContext } from './xxModel.ts';
exort const ComponentB = () => {
const { addOne } = useContext();
return <button onClick={addOne}>
点我 +1
</button>
}
创作不易
如果你觉得这篇文章对你有帮助的话,欢迎点赞、收藏、关注,你的支持将会是我更新的动力。