React 中 useContext 的使用方法
在 React 中,useContext 是一个内置的 Hook,用于在函数组件中轻松访问 Context(全局公共状态),避免了手动逐层传递 props 的复杂性。它依赖于 Context API,通过 Provider 提供数据,后代组件通过 useContext 消费数据。以下是详细的使用方法和步骤,基于 React 官方指南和实践经验。
1. 创建 Context 对象
首先需要使用 React.createContext 创建一个 Context 对象。这个对象包含 Provider 和 Consumer 组件,但 useContext 简化了消费过程。
javascript
import React from 'react';
// 创建Context,可设置默认值(可选)
const MyContext = React.createContext(defaultValue);
defaultValue是当组件上方无 Provider 时的回退值,通常设为null或初始状态。
2. 使用 Provider 提供数据
在父组件中,用 <Context.Provider> 包裹子组件,并通过 value 属性传递数据。Provider 必须位于调用 useContext 的组件之上。
javascript
import React from 'react';
import ChildComponent from './ChildComponent';
import MyContext from './MyContext';
function ParentComponent() {
const sharedData = { theme: 'dark', user: 'Alice' }; // 共享数据
return (
<MyContext.Provider value={sharedData}>
<ChildComponent /> {/* 后代组件可访问sharedData */}
</MyContext.Provider>
);
}
- 注意 :Provider 的
value变化时,所有消费该 Context 的组件会自动重新渲染。
3. 在后代组件中使用 useContext 消费数据
在后代组件中,导入 Context 对象并调用 useContext,直接获取 Provider 提供的 value。
javascript
import React, { useContext } from 'react';
import MyContext from './MyContext'; // 导入父组件中的Context
function ChildComponent() {
const publicData = useContext(MyContext); // 调用useContext获取数据
return (
<div>
<p>当前主题: {publicData.theme}</p>
{/* 示例:渲染图片或其他UI */}
<img src="image-path" alt="示例" style={{ width: '50px', marginLeft: '10px' }} />
</div>
);
}
- 关键点 :
useContext(MyContext)返回最近的 Provider 的value;若无 Provider,则返回defaultValue。- 代码简洁,无需嵌套
<Context.Consumer>。 - Context 变化时,React 会触发组件重新渲染,确保数据最新。
4. 完整代码示例
整合以上步骤,一个简单应用:
javascript
// 文件: Context.js
import React from 'react';
export const ThemeContext = React.createContext({ theme: 'light' });
// 文件: App.js (父组件)
import React from 'react';
import { ThemeContext } from './Context';
import Child from './Child';
function App() {
return (
<ThemeContext.Provider value={{ theme: 'dark' }}>
<Child />
</ThemeContext.Provider>
);
}
// 文件: Child.js (后代组件)
import React, { useContext } from 'react';
import { ThemeContext } from './Context';
function Child() {
const { theme } = useContext(ThemeContext);
return <div>当前主题: {theme}</div>; // 输出: 当前主题: dark
}
在类组建中,useContext 的使用方法
在类组件中使用 Context 有两种方式:
- 使用
static contextType属性(只能订阅单一 Context) - 使用
Context.Consumer(可订阅多个 Context)
而在函数组件中,我们使用 useContext 钩子(可订阅多个 Context)。
下面我将详细说明类组件中使用 Context 的方法,并对比函数组件中的使用差异。
1、使用 static contextType(单一 Context 订阅)
步骤:
- 创建 Context:
const MyContext = React.createContext(defaultValue); - 在类组件中通过
static contextType = MyContext;指定要订阅的 Context - 通过
this.context访问 Context 的值
示例代码
javascript
import React from 'react';
// 创建Context
const ThemeContext = React.createContext('light');
class MyClassComponent extends React.Component {
static contextType = ThemeContext; // 关键:静态属性赋值
render() {
const theme = this.context; // 通过this.context访问
return <div>当前主题: {theme}</div>;
}
}
// 在父组件中提供Context
function App() {
return (
<ThemeContext.Provider value="dark">
<MyClassComponent />
</ThemeContext.Provider>
);
}
2、使用 Context.Consumer(支持多个 Context)
步骤:
- 在类组件的
render方法中,使用<MyContext.Consumer>组件包裹 - 内部使用函数作为子元素(render prop 模式)
示例代码:
javascript
import React from 'react';
// 创建两个Context
const ThemeContext = React.createContext('light');
const UserContext = React.createContext('Guest');
class MyClassComponent extends React.Component {
render() {
return (
// 消费多个Context
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<div>
主题: {theme}, 用户: {user}
</div>
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
}
// 在父组件中提供多个Context
function App() {
return (
<ThemeContext.Provider value="dark">
<UserContext.Provider value="Alice">
<MyClassComponent />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
3、类组件与函数组件使用 Context 的主要区别
| 特性 | 类组件 | 函数组件 |
|---|---|---|
| 订阅方式 | 1. static contextType + this.context(单一)2. Context.Consumer(支持多个) |
useContext 钩子(支持多个) |
| 多个 Context 使用 | 使用 Context.Consumer 嵌套较深 |
直接多次调用 useContext,简洁清晰 |
| 代码简洁性 | 相对冗长,尤其是多个 Context 时 | 非常简洁 |
| 组件类型限制 | static contextType 仅适用于类组件(引用[1]) |
useContext 仅适用于函数组件 |
| 动态更新 | 当 Context 更新时,组件都会重新渲染 | 同样重新渲染,但可通过 React.memo 优化 |
4、useContext 实现原理详解
useContext 的实现原理基于 React 的 上下文机制(Context) 和 订阅-发布模式,主要涉及三个核心环节:
1. Context 对象的内部结构
每个通过 createContext() 创建的 Context 对象包含以下关键属性:
javascript
const MyContext = React.createContext(defaultValue);
// 内部结构:
{
_currentValue: defaultValue, // 当前值存储
_threaded: true, // 标识当前渲染线程
Provider: { ... }, // Provider 组件
Consumer: { ... }, // Consumer 组件
_currentRenderer: null, // 当前渲染器
_globalName: null, // 全局名称
_subscribe: function() { ... } // 订阅函数
}
核心是 _currentValue (存储当前值) 和 _subscribe (管理订阅者链表)
2. 值读取与订阅机制
当调用 useContext(MyContext) 时:
javascript
function useContext(Context) {
// 1. 从 Context._currentValue 读取当前值
const value = readContext(Context);
// 2. 将当前组件添加到订阅链表
subscribeToContext(Context, currentlyRenderingFiber);
return value; // 返回上下文值
}
currentlyRenderingFiber 是 React 内部的一个全局变量 ,用于指向当前正在执行的函数组件所对应的 Fiber 节点。它的主要作用是在函数组件渲染过程中为 Hooks 提供访问当前组件状态的桥梁。
具体过程:
- 读取值 :直接访问
Context._currentValue获取最新值 - 建立订阅 :将当前函数组件对应的 Fiber 节点添加到 Context 的订阅者链表
- 通过
currentlyRenderingFiber.dependencies链表维护订阅关系 - 每个依赖项包含
context指针和订阅状态
- 通过
3. 更新触发流程
当 Provider 的值更新时:
javascript
<MyContext.Provider value={newValue}>
// 1. 更新 Context._currentValue = newValue
// 2. 遍历订阅者链表 (Context._subscribe)
// 3. 标记所有订阅组件的 Fiber 节点为需要更新
// 4. 触发重新渲染
关键点:
- 批量更新:React 会合并多个 Context 更新,避免频繁渲染
- 精准更新:只更新订阅该 Context 的组件(通过 Fiber 依赖链)
- 默认值处理 :无 Provider 时返回
createContext(defaultValue)的默认值