一、核心作用
1. context 定义
createContext 是 React 内置 API,核心目的是 跨组件共享数据 ,避免 "父→子→孙" 逐层传递 props 属性透传 的繁琐问题(即 "prop drilling")。简单说:它能创建一个 "公共数据盒子",顶层组件一次提供数据,所有嵌套子组件都能直接读取,不用中间组件转发。
二、context 命名
1. 为啥偏偏叫「UserContext」?
因为这个 Context 的核心用途是 存储和共享 "用户相关的信息" (比如之前例子里的用户名、年龄、修改用户的方法),名字和用途完全对应:
- 前缀
User:明确告诉别人 "这个上下文里存的是用户相关的数据"; - 后缀
Context:明确告诉别人 "这是一个 React Context 容器"(不是普通变量、不是组件)。
别人看代码时,只要看到 UserContext,不用点进去看里面的内容,就能立刻知道:"哦,这是专门用来共享用户数据的上下文,子组件用它能拿用户信息 / 改用户信息"。
2. 不这么命名会怎么样?
如果乱起名,比如:jsx
ini
// 反面例子1:名字太模糊,不知道存啥
const MyContext = createContext({});
// 反面例子2:只说用途不说类型,不知道是啥东西
const UserData = createContext({});
// 反面例子3:完全无关,让人困惑
const ABC123 = createContext({});
- 你自己过 1 个月再看代码,可能忘了
MyContext里存的是用户数据还是主题数据; - 同事接手你的代码时,得逐行找
Provider里的value才知道这个上下文的用途,浪费时间
3. 命名的通用规则(举一反三)
Context 的命名套路很固定:[数据类型/用途] + Context,目的是 "见名知意"。比如:
- 存主题配置(亮 / 暗色、字体大小)→ 叫
ThemeContext; - 存用户权限(是否登录、角色)→ 叫
AuthContext; - 存全局设置(语言、通知开关)→ 叫
SettingsContext; - 存购物车数据(商品列表、结算方法)→ 叫
CartContext。
4. 命名总结
const UserContext = createContext({}) 命名为 UserContext,本质是「用名字传递信息」:
- 不用死记硬背,看到名字就知道 "存什么、是什么类型";
- 是 React 开发的通用约定,让代码更规范、更容易维护;
- 不是 React 的语法要求(改别的名字代码也能跑),但这是 "专业写法" 的基本要求~
三、核心概念
结合代码,代码在最后(parent.jsx/child.jsx/grandChild.jsx),3 个角色对应关系如下:
1. "储物盒" 的 3 个概念
| 角色 | 作用 | 代码示例 |
|---|---|---|
createContext |
创建 "公共数据盒子"(Context) | const UserContext = createContext({}) |
Context.Provider |
给盒子装数据,并用它包裹子组件(分发数据) | <UserContext.Provider value={sharedData}> |
useContext |
子组件从盒子里直接拿数据 | useContext(UserContext) |
2. 储物盒" 的 3 个关键用法
-
造盒子 :
const UserContext = createContext({})→ 新建一个叫 "用户相关" 的储物盒,初始先放个空盒子占位; -
装东西 + 发下去 :用
<UserContext.Provider value={共享数据}>→ 给盒子里装上要共享的东西(比如你的用户信息 + 修改方法),再把盒子 "递到所有子组件面前"(用 Provider 包裹子组件); -
直接拿东西 :子组件里用
useContext(UserContext)→ 不用问父组件要,直接从这个公共盒子里拿东西,不管嵌套多少层都能拿。
3. 储物盒大白话总结
- 不用再 "父传子、子传孙" 一层层递数据(解决之前 props 递来递去的麻烦);
- 一个盒子专门装一类东西(比如用户数据放 UserContext,主题放 ThemeContext),不乱;
- 只要在 "发盒子"(Provider)的范围内,所有组件都能直接用,省事儿。
一句话收尾:createContext 就是 React 给你的 "共享储物盒工具",造个盒子、装进去东西、大家直接拿,不用挨个递~
四、实战示例
1. 文件结构
项目文件结构(同目录下 3 个文件):
UseContextTest/
├─ parent.jsx(父组件,创建Context+提供数据)
├─ child.jsx(子组件,读取数据)
└─ grandChild.jsx(孙组件,读取数据)
2. 父组件:创建 Context + 提供数据(parent.jsx)
javascript
// 1. 导入 React 和需要的 Hook(createContext 用来创建上下文,useContext 用来读取,useState 用来管理状态)
import { createContext, useState, } from 'react';
import Child from './child';
// 2. 创建一个 Context(相当于一个"数据容器",专门存要共享的东西)
// 传入默认值(只有没找到 Provider 时才会用,这里先写个空对象占位)
const UserContext = createContext({});
// 3. 父组件(顶层组件,负责提供数据)
export default function UseContextTest() {
// 定义要共享的状态:当前用户信息
const [user, setUser] = useState({
name: '小明',
age: 10,
grade: '小学四年级'
});
// 定义要共享的方法:修改用户信息(子组件可以调用这个方法改数据)
const changeUser = (newName, newAge) => {
setUser({
...user, // 保留原来的 grade,只改 name 和 age
name: newName,
age: newAge
});
};
// 把要共享的数据和方法打包成一个对象
const sharedData = {
userInfo: user, // 共享的用户数据
updateUser: changeUser // 共享的修改方法
};
return (
<div style={{ padding: 20 }}>
<h1>父组件(App)</h1>
<p>当前共享的用户:{user.name},{user.age}岁</p>
{/* 4. 用 Provider 把数据"注入"给子组件 */}
{/* value 属性:传入要共享的对象(必须写这个,子组件才能拿到) */}
<UserContext.Provider value={sharedData}>
{/* 子组件:不用传 props,直接能拿到共享数据 */}
<Child />
</UserContext.Provider>
</div>
);
}
// 关键:导出 UserContext,供子组件导入使用!
export { UserContext }
3. 子组件:读取共享数据(child.jsx)
javascript
import { useContext } from 'react';
import { UserContext } from './parent'; // 重点:路径+解构都不能错!
import GrandChild from './grandChild'; // 引入孙组件
// 5. 子组件(中间层,直接读取共享数据)
export default function Child() {
// 用 useContext 读取共享数据(参数就是上面创建的 UserContext)
const { userInfo, updateUser } = useContext(UserContext);
return (
<div style={{ padding: 20, margin: 20, border: '1px solid #ccc' }}>
<h2>子组件(Child)</h2>
<p>从 Context 拿到的用户:{userInfo.name},{userInfo.grade}</p>
{/* 调用共享的方法,修改数据 */}
<button onClick={() => updateUser('小红', 11)}>
点击修改用户为"小红"
</button>
{/* 孙组件:同样不用传 props,直接访问共享数据 */}
{/* <GrandChild /> 已经在顶层 Provider 的 "覆盖范围" 里了,不需要重复包裹! */}
<GrandChild />
</div>
);
}
4. 孙组件:跨层级读取数据(grandChild.jsx)
javascript
import { useContext } from 'react';
import { UserContext } from './parent'; // 重点:路径+解构都不能错!
// 6. 孙组件(最底层,跨两层拿到数据)
export default function GrandChild() {
// 同样用 useContext 读取,和子组件写法一样
const { userInfo, updateUser } = useContext(UserContext);
return (
<div style={{ padding: 20, margin: 20, border: '1px solid #ddd' }}>
<h3>孙组件(GrandChild)</h3>
<p>跨两层拿到的用户:{userInfo.name},{userInfo.age}岁</p>
{/* 调用共享的方法,修改数据 */}
<button onClick={() => updateUser('小刚', 12)}>
点击修改用户为"小刚"
</button>
</div>
);
}
5. 运行效果
-
父、子、孙组件都能显示同一个用户信息;
-
点击任意组件的 "修改" 按钮,所有组件的用户信息会同步更新;
-
中间组件(Child)无需传递
props,只负责渲染子组件即可。
6.关键注意事项(避坑指南)
- Context 不能从 react 导入 :
UserContext是你自己用createContext创建的,不是 React 内置 API,导入时要从父组件文件导入(如import { UserContext } from './parent')。 - Provider 必须包裹子组件 :只有被
<UserContext.Provider>包裹的组件,才能用useContext读取数据,否则只能拿到createContext的默认值。 - 无需重复包裹 Provider :孙组件不用再套
Provider,只要在顶层(父组件)包一次,数据会自动穿透所有嵌套子组件。 - 导入路径要正确 :子组件 / 孙组件导入
UserContext时,路径要和文件实际位置对应(比如同目录写./parent,上级目录写../parent)。 - 命名规范 :Context 命名建议用「用途 + Context」(如
UserContext存用户数据,ThemeContext存主题数据),见名知意。
五、核心总结
1. 3步走
createContext 的核心逻辑就是 "3 步走":
- 用
createContext()造一个 "公共数据盒子"; - 用
<Context.Provider value={数据}>给盒子装东西,并分发到子组件; - 用
useContext(Context)在任意子组件(无论嵌套多深)直接拿东西。
它解决了 "prop drilling" 的痛点,适合中小型项目的全局数据共享(如用户信息、主题配置等),用法简洁且无需依赖第三方库。
2. 注意项
在 React 19 及之后的版本中,createContext 创建的 Context 对象可以直接作为 Provider 组件使用 (等价于原来的 Context.Provider),所以不需要再写 .Provider 了。
核心原因:React 19 的语法简化
在 React 18 及更早版本中,必须显式写 Context.Provider 才能传递数据;但 React 19 为了简化代码,新增了「Context 对象直接作为 Provider 组件」的语法,此时:
xml
// React 19 新写法(等价于旧写法的 Context.Provider)
<ThemeContext value={theme}>
{/* 子组件 */}
</ThemeContext>
完全等价于 React 18 及之前的写法:
xml
// React 18 及更早的写法
<ThemeContext.Provider value={theme}>
{/* 子组件 */}
</ThemeContext.Provider>
补充说明
这个语法只是写法简化 ,功能和原来的 Context.Provider 完全一致:
- 依然需要通过
value属性传递数据; - 依然会向下广播数据,后代组件用
useContext依然能正常获取值; - 仅在 React 19 及之后版本支持(如果你的项目是 React 18 及以下,还是得写
.Provider)。