context API 概念
Context 主要由三个部分组成:React.createContext 方法、Context.Provider 组件和 Context.Consumer 组件。
定义Context-React.createContext:
用于创建Context对象的方法。接受一个默认值作为参数,并返回一个包含Provider和Consumer组件的对象。默认值在组件树中找不到匹配的Provider时被使用。
// 创建一个名为 MyContext 的 Context 对象,并指定默认值为 "default value"
const MyContext = React.createContext("default value");
赋值Context-context.Provider组件:
Context.Provider 是用于提供数据的组件,它接收一个 value 属性作为数据的值。Provider 组件将 value 中的数据传递给其子组件中的所有 Consumer 组件。当 Provider 的 value 发生变化时,所有依赖该 Provider 的 Consumer 组件都会重新渲染。
import { MyContext } from './MyContext';
function App() {
// 使用 MyContext.Provider 提供共享数据
return (
<MyContext.Provider value="Hello from Context">
<ChildComponent />
</MyContext.Provider>
);
}
消费Context-Context.Consumer组件:
Context.Consumer 组件用于在组件树中消费共享的数据。它必须包含在 Context.Provider 内部,并且使用函数作为其子元素。这个函数接收当前的 Context 值作为参数,并返回 React 元素。当 Context.Provider 的 value 发生变化时,Context.Consumer 组件会重新渲染,获取最新的 Context 值。
Consumer 组件通过函数作为子组件的方式,将 Provider 提供的值作为该函数的参数,可以在函数体内使用该值。
import { MyContext } from './MyContext';
function ChildComponent() {
// 使用 MyContext.Consumer 消费共享数据
return (
<MyContext.Consumer>
{value => <div>{value}</div>}
</MyContext.Consumer>
);
}
Context使用场景
深层嵌套:数据需要传递超过3-4层组件多处使用:多个不相关的组件需要同样的数据全局状态:主题、用户认证、语言设置等全局状态避免中间组件污染:中间件不应该知道传递的数据
Context不建议使用场景
频繁变化的数据:可能导致大量重新渲染组件库开发:增加使用复杂度简单的父子通信:直接使用props更清晰
Context的性能问题
在大型应用中,React Context 可能会面临性能问题,主要涉及以下几个方面:
- 嵌套层级过深:当
Context嵌套层级过深时,每个Consumer组件在渲染时都需要检查其上层是否存在匹配的 Provider 组件。这种嵌套关系会增加渲染的开销,特别是在庞大的组件树中。 - 未优化的更新触发:如果
Context的值发生变化时,所有依赖该Context的Consumer组件都会触发重新渲染。在某些情况下,这可能会导致无关的组件重新渲染,造成性能浪费。
针对Context性能问题,可以采取以下优化策略:
-
- 避免过深的嵌套层级:尽量避免在组件树中创建过深的
Context嵌套结构。可以通过重构组件结构、合并多个Context,或者使用更细粒度的Context来减少嵌套层级。
- 避免过深的嵌套层级:尽量避免在组件树中创建过深的
-
- 使用 shouldComponentUpdate 或 React.memo:在
Consumer组件中,可以使用shouldComponentUpdate或React.memo来进行性能优化。通过对比前后的Context值,可以避免不必要的重新渲染。
- 使用 shouldComponentUpdate 或 React.memo:在
-
- 使用 useContext 和 useReducer:在性能要求较高的情况下,可以使用
useContext配合useReducer来替代useContext配合useState。因为useReducer提供了更细粒度的更新控制,可以减少不必要的重新渲染。
- 使用 useContext 和 useReducer:在性能要求较高的情况下,可以使用
-
- 使用局部化的 Context:对于大型应用中的某些模块或功能,可以使用局部化的
Context,而不是将Context放置在整个应用的顶层。这样可以减少不必要的Consumer组件,提高渲染性能。
- 使用局部化的 Context:对于大型应用中的某些模块或功能,可以使用局部化的
-
- 使用数据分离:对于只读的全局数据,可以考虑使用状态管理库(如
Redux或MobX)来管理,而不是使用Context。这样可以提供更高效的状态管理和更新机制。
- 使用数据分离:对于只读的全局数据,可以考虑使用状态管理库(如
Context 原理
- Provider 传递流程:Provider 的更新,会 深度遍历子代 fiber,消费 context 的 fiber 和父级链都会 提升更新优先级。 对于类组件的 fiber ,会 forceUpdate 处理。接下来所有消费的 fiber,都会 beginWork 。
- Context 订阅流程: contextType , useContext, Consumer 会内部调用 readContext ,readContext 会把 fiber 上的 dependencies 属性 和 context 对象 建立起关联。