|
|
|
# 简单的 React 思考 - Context
|
|
|
|
关于 React 中 Context 作为状态管理工具的思考具体可以这一篇 [[React 状态管理工具的简单思考]]
|
|
|
|
这里主要是对 Context API 的简单记录
|
|
|
|
|
|
|
|
|
|
|
|
### React.createContext / Context.Provider
|
|
|
|
```javascript
|
|
|
|
const MyContext = React.createContext(defaultValue)
|
|
|
|
```
|
|
|
|
|
|
|
|
createContext 会创建一个 Context 对象,每个 Context 对象都会返回一个 Provieder 组件,它的子组件会订阅 Context 的变化
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
<MyContext.Provider value={value} />
|
|
|
|
```
|
|
|
|
|
|
|
|
订阅了 Context 的组件会在组件树中查找离自己最近的 Provider 中读取到 Context 值也就是 Provider 中的 value 属性,只有在找不到 Provider 时,createContext 中的 defaultValue 参数才会生效,但是将 underfined 传递给 Provider 的时候,defaultValue 并不会生效。
|
|
|
|
多个 Provider 可以嵌套使用,里层的会覆盖外层的数据。
|
|
|
|
当 Provider 中的 value 值发生变化时,它内部的所有消费组件也就是子组件都会重新渲染,这个用于判断值是否发生变化的方法和 Object.is 使用了同样的算法, 也就是说如果 value 是一个引用类型,可能会导致一些意外的问题。
|
|
|
|
|
|
|
|
|
|
|
|
### Class.contextType / Context.Consumer
|
|
|
|
在 Hook 之前,在 React 组件中使用和消费 Context 的值可以使用 contextType 和 Consumer。
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
class MyClass extends React.Component {
|
|
|
|
render() {
|
|
|
|
const value = this.context;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
MyClass.contextType = MyContext;
|
|
|
|
```
|
|
|
|
|
|
|
|
contextType 属性可以让类组件使用 this.context 来获取 **最近**的 Context 上的值,并且可以在任何生命周期中访问到它
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
<MyContext.Consumer>
|
|
|
|
{ value => /* 基于 context 值进行渲染*/ }
|
|
|
|
</MyContext.Consumer>
|
|
|
|
```
|
|
|
|
|
|
|
|
Consumer 是函数式组件订阅 Context 的方法。
|
|
|
|
|
|
|
|
|
|
|
|
### Hook
|
|
|
|
之前说了,contextType 和 Consumer 都是在 Hook 出现之前订阅和消费 Context 的方法,那么在 Hook 出现之后,函数式组件自然也有了 Hook 的方法用于消费 Context
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
const value = useContext(MyContext);
|
|
|
|
```
|
|
|
|
|
|
|
|
useContext 接受一个 Context 对象,并返回该 Context 的当前值,和 contextType 中相同,Context 上的值由最近的 Provider 决定,当组件最上层的 Provider 更新时,该 Hook 会触发重新渲染,并使用最新传递的 value,且无视上层父组件使用了 React.memo,也就是说使用了 useContext 的组件总会在 context 值变化的时候重新渲染。
|
|
|
|
如果重新渲染组件的开销较大,可以将组件放在 useMemo 中来优化
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
return useMemo(() => {
|
|
|
|
const appContextValue = useContext(AppContext);
|
|
|
|
return <ExpensiveTree className={appContextValue.theme} />;
|
|
|
|
}, [theme])
|
|
|
|
```
|
|
|
|
|
|
|
|
useContext Hook 相当于 class 组件中 Class.contextType / Context.Consumer,但是对于函数式组件,提供了更加优雅的 Context 使用方案,但是只是提供了 Context 的读取和订阅,你仍然需要在上层组件树中使用 Context.Provider
|