React Context 详解:从 Props 传递到跨组件状态共享
学习目标:理解 React Context 的核心原理,掌握
createContext、Provider、useContext,并能在真实业务项目中使用。学习建议:Context 属于 React 的重要基础知识,重点掌握实际使用方式,不需要深入研究底层源码。
一、Context 是什么?
React Context 是 React 提供的一种跨组件层级传递数据的机制。
正常情况下,React 组件之间通过 Props 传递数据。
例如:
App
└── Layout
└── Header
└── UserInfo
假设 UserInfo 组件需要获取当前登录用户的信息。
如果使用 Props,就需要把 user 一层层传递下去:
function App() {
const user = { name: "Kevin" };
return <Layout user={user} />;
}
function Layout({ user }) {
return <Header user={user} />;
}
function Header({ user }) {
return <UserInfo user={user} />;
}
function UserInfo({ user }) {
return <div>{user.name}</div>;
}
存在的问题:
Layout不需要使用user,却必须接收和转发。Header也不需要使用user,却必须继续传递。- 组件层级越深,传递越繁琐。
这种情况叫 Props Drilling(Props 逐层传递) 。
Context 就是用来解决这类问题的。
二、Context 的基本原理
使用 Context 后,上层组件可以提供数据,下层组件直接读取,不需要中间组件逐层转发 Props。
App
│
├── UserContext.Provider
│ │
│ ▼
└── Layout
└── Header
└── UserInfo
│
└── useContext(UserContext)
你可以将它理解为:
Provider 提供一个共享的数据上下文,后代组件可以通过 useContext 读取。
需要注意,Context 不是把数据挂到 window 上,而是由 React 根据组件树中的 Provider 关系进行传递。
三、Context 的三个核心 API
| API | 作用 | 重要程度 |
|---|---|---|
createContext() | 创建 Context 对象 | P0 |
Provider | 为后代组件提供数据 | P0 |
useContext() | 读取 Context 数据 | P0 |
第一步:创建 Context
创建 UserContext.jsx:
import { createContext } from "react";
export const UserContext = createContext(null);
这里:
createContext(null)
表示创建一个 Context,默认值是 null。
默认值只会在组件上方没有匹配的 Provider 时使用。
第二步:通过 Provider 提供数据
import { UserContext } from "./UserContext";
import Layout from "./Layout";
function App() {
const user = {
name: "Kevin",
role: "admin"
};
return (
<UserContext.Provider value={user}>
<Layout />
</UserContext.Provider>
);
}
核心代码:
<UserContext.Provider value={user}>
<Layout />
</UserContext.Provider>
含义是:让 Layout 以及它的后代组件能够获取 user。
React 19 也支持直接使用 <UserContext value={user}> 提供数据;这里使用兼容 React 18 的经典 Provider 写法。
第三步:useContext 读取数据
import { useContext } from "react";
import { UserContext } from "./UserContext";
function UserInfo() {
const user = useContext(UserContext);
return (
<div>
当前用户:{user?.name ?? "未登录"}
</div>
);
}
这里:
const user = useContext(UserContext);
意思是:
从当前组件上方最近的 UserContext.Provider 中读取 value。
即使中间隔了很多层组件,也不需要逐层传递。
四、实战:Context + useState 实现主题切换
前面只演示了读取固定数据。
在实际项目中,Context 经常和 useState 配合,实现可以动态更新的共享状态。
例如多个组件需要共享深色、浅色主题。
1. 创建 ThemeContext
// ThemeContext.jsx
import {
createContext,
useContext,
useState
} from "react";
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme(prev =>
prev === "light" ? "dark" : "light"
);
};
return (
<ThemeContext.Provider
value={{ theme, toggleTheme }}
>
{children}
</ThemeContext.Provider>
);
}
// 自定义 Hook
export function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error("useTheme 必须在 ThemeProvider 内使用");
}
return context;
}
这里有几个重点:
useState:真正保存主题状态。ThemeContext:负责跨组件传递状态。ThemeProvider:提供状态和操作函数。useTheme:封装 useContext,让业务组件调用更简单。
2. 在 App 中使用 Provider
import { ThemeProvider } from "./ThemeContext";
import Header from "./Header";
import Content from "./Content";
function App() {
return (
<ThemeProvider>
<Header />
<Content />
</ThemeProvider>
);
}
3. 在不同组件中读取和修改状态
Header.jsx:
import { useTheme } from "./ThemeContext";
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header>
当前主题:{theme}
<button onClick={toggleTheme}>
切换主题
</button>
</header>
);
}
export default Header;
Content.jsx:
import { useTheme } from "./ThemeContext";
function Content() {
const { theme } = useTheme();
return (
<main
style={{
background: theme === "dark" ? "#222" : "#fff",
color: theme === "dark" ? "#fff" : "#222",
padding: 20
}}
>
任务管理系统
</main>
);
}
export default Content;
这样,Header 点击按钮修改主题后,Content 也会自动更新。
整个过程中,Header 和 Content 不需要互相传递 Props。
五、Context 更新后为什么组件会变化?
理解这个流程即可:
用户点击切换主题
↓
调用 toggleTheme()
↓
setTheme("dark")
↓
ThemeProvider 重新渲染
↓
Context 的 value 发生变化
↓
使用该 Context 的后代组件更新
↓
页面显示深色主题
Context 负责传递数据,useState 负责保存和更新数据。
这是非常重要的区别。
Context 自身并不是一个独立的状态管理引擎。
六、Context 和 Zustand 有什么区别?
你前面了解过 Zustand,这里可以对比一下。
| 对比 | Context | Zustand |
|---|---|---|
| 是否 React 内置 | 是 | 否,需要安装 |
| 核心作用 | 跨层级传递数据 | 管理共享客户端状态 |
| 是否需要 Provider | 通常需要 | 常规使用不需要 |
| 状态更新 | 通常配合 useState | Store 内置 set |
| 订阅方式 | 读取整个 Context 值 | 可以通过 selector 选择部分状态 |
| 适合场景 | 主题、语言、用户上下文 | 多模块共享、频繁更新的状态 |
例如任务管理平台:
适合 Context:
- 用户信息
- 系统主题
- 多语言配置
- 某个业务模块的公共配置
适合 Zustand:
- 多页面共享的任务筛选条件
- 全局选中任务
- 复杂客户端交互状态
- 多个组件需要频繁更新的共享数据
不过不代表 Zustand 一定比 Context 好。
对于简单共享数据,Context 已经足够。
七、Context 使用时需要注意什么?
1. 不要把所有状态都放进一个 Context
例如:
<AppContext.Provider
value={{
user,
theme,
tasks,
filters,
selectedTask,
loading
}}
>
<App />
</AppContext.Provider>
如果这些值频繁发生变化,所有读取该 Context 的组件都有可能因为 value 变化而重新渲染。
更合理的方式是按照职责拆分:
UserContext
ThemeContext
TaskContext
Context 不支持像 Zustand 一样直接通过 selector 对单个字段进行细粒度订阅。
2. Provider 必须位于消费者上方
<UserContext.Provider value={user}>
<UserInfo />
</UserContext.Provider>
这样 UserInfo 才能读取这个 Provider 提供的值。
如果组件上方没有匹配的 Provider,就会读取 createContext 指定的默认值。
3. 不要滥用 Context
如果两个组件只是简单的父子关系:
<TaskCard task={task} />
直接使用 Props 往往更清晰。
没有必要为了传递一个参数,就专门创建一个 Context。
八、Context、Props、useState 怎么选择?
可以直接按照下面的规则判断。
| 场景 | 推荐方式 |
|---|---|
| 输入框内容变化 | useState |
| 父组件传任务给子组件 | Props |
| 多层组件共享用户信息 | Context |
| 全局主题切换 | Context + useState |
| 多模块复杂共享状态 | Zustand |
| 后端任务列表请求与缓存 | SWR / TanStack Query |
实际工作中的原则:
状态能放在局部,就尽量放在局部;需要跨层级共享,再考虑 Context;共享状态复杂时,再考虑 Zustand。
九、React Context 学习总结
你只需要记住以下三个 API:
// 1. 创建 Context
const UserContext = createContext(null);
// 2. 提供数据
<UserContext.Provider value={user}>
<App />
</UserContext.Provider>
// 3. 读取数据
const user = useContext(UserContext);
再记住三个核心区别:
- Props:适合父子组件之间传递数据。
- Context:适合跨组件层级传递共享数据。
- Zustand:适合管理复杂的共享客户端状态。
对于 React + TypeScript 中后台开发,Context 学到这里就基本够用了。
最重要的不是熟记 API,而是能够判断:这个状态应该放在组件内部、通过 Props 传递,还是放进 Context / Zustand 共享。