React(十二):Context 完全指南 —— 跨组件数据共享的正确姿势
适用版本:React 19
前置知识:函数组件、useState、TypeScript 基础
学习目标:掌握 Context 的核心机制、完整实现流程、React 19 新特性、常见坑点与最佳实践
目录
一、为什么需要 Context:从 props 透传之痛说起
1.1 问题:props 逐层透传(prop drilling)
假设我们有一个 App 组件,要把当前登录用户信息传给最底层的 Avatar 组件。中间经过 Header、UserMenu 两层组件。
不使用 Context 的写法
// App.tsx:源头拥有数据
function App() {
const [user] = useState({
id: 1,
name: "张三",
avatar: "https://example.com/avatar.jpg",
});
return <Layout user={user} />;
}
// Layout.tsx:不需要 user,但必须帮忙往下传
function Layout({ user }: { user: User }) {
return (
<div className="layout">
<Header user={user} />
<main>主要内容区域</main>
</div>
);
}
// Header.tsx:也不需要 user,继续往下传
function Header({ user }: { user: User }) {
return (
<header>
<Logo />
<UserMenu user={user} />
</header>
);
}
// UserMenu.tsx:接近目标了,继续传
function UserMenu({ user }: { user: User }) {
return (
<div className="user-menu">
<span>{user.name}</span>
<Avatar user={user} />
</div>
);
}
// Avatar.tsx:真正需要 user 的地方
function Avatar({ user }: { user: User }) {
return <img src={user.avatar} alt={user.name} className="avatar" />;
}
痛点分析:
| 痛点 | 说明 |
|---|---|
| 中间组件被迫写样板代码 | Layout 和 Header 根本不需要 user,但为了传给子组件必须接收再转发 |
| 耦合度提高 | 中间组件的 props 签名与最终消费者强行绑定 |
| 难以维护 | 如果有一天 UserMenu 改成兄弟组件共享,需要改动的层级很多 |
| 命名污染 | 每一层都要起 user 这个 prop 名字,容易和本层自己的状态混淆 |
1.2 Context 的解决价值
Context 提供了一种跨组件直接共享数据的能力,让数据生产者(Provider)和消费者可以直接"隔空对话",无需中间组件层层转发。
// 使用 Context 后,Layout 和 Header 完全不需要知道 user 的存在
function App() {
return (
<UserProvider>
<Layout />
</UserProvider>
);
}
function Layout() {
return (
<div className="layout">
<Header />
<main>主要内容区域</main>
</div>
);
}
function Header() {
return (
<header>
<Logo />
<UserMenu />
</header>
);
}
function UserMenu() {
return (
<div className="user-menu">
<UserName />
<Avatar />
</div>
);
}
一句话总结:Context 是 React 提供的"跨层级广播"机制,让共享数据像 radio 信号一样,谁需要谁接收。
1.3 Context 适合与不适合的场景
✅ 适合使用 Context 的场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 全局用户状态 | 登录态、用户信息、权限角色 | 当前登录用户、token |
| 主题配置 | 亮色/暗色模式、颜色变量、字体大小 | 全局主题切换 |
| 多语言设置 | 当前语言、翻译函数 | i18n 切换 |
| 全局 UI 状态 | 弹窗、通知、toast、侧边栏展开 | 全局消息通知中心 |
| 配置类信息 | 不需要频繁变化的 app 配置 | 站点名称、API 基础地址 |
❌ 不适合使用 Context 的场景
| 场景 | 原因 | 推荐方案 |
|---|---|---|
| 父子组件之间简单传参 | 杀鸡用牛刀,引入复杂度 | 直接使用 props |
| 高频变化的状态 | Provider 的 value 变化会导致所有消费者重渲染 | 使用状态管理库(Zustand、Jotai、Redux Toolkit) |
| 组件复用性要求高的数据 | Context 让组件依赖外部上下文,难以单独复用 | 通过 props 传入或组合组件 |
| 仅用于避免少量 props 传递 | 层数不多的情况下 props 更清晰 | props 透传 |
二、React 19 Context 完整实现流程
2.1 创建 Context
Context 创建使用 React.createContext() 方法。React 19 中这个方法与之前版本保持一致,但结合 TypeScript 使用更规范。
基本语法
const MyContext = createContext(defaultValue);
| 参数 | 说明 |
|---|---|
defaultValue |
当组件没有匹配到任何 Provider 时使用的默认值 |
| 返回值 | 一个 Context 对象,包含 Provider 和 Consumer 两个属性 |
示例:创建 ThemeContext(带 TypeScript)
import { createContext } from "react";
// 1. 定义数据类型
type Theme = "light" | "dark";
interface ThemeContextValue {
theme: Theme;
toggleTheme: () => void;
}
// 2. 创建 Context,传入默认值
// 默认值会在组件没有匹配到 Provider 时生效
const ThemeContext = createContext<ThemeContextValue>({
theme: "light", // 默认主题
toggleTheme: () => {}, // 默认空函数,避免调用时报错
});
export default ThemeContext;
默认值(defaultValue)的 fallback 逻辑
// 场景:某组件被直接使用,没有被 Provider 包裹
function SomeComponent() {
const { theme } = useContext(ThemeContext);
return <div>当前主题:{theme}</div>; // 输出:当前主题:light
}
重要认知:默认值只有在没有 Provider 包裹时才会生效。如果 Provider 的
value是undefined,默认值也不会生效——React 会认为你显式传了undefined。
最佳实践:把 undefined 也纳入类型考虑
// 如果你希望消费者必须被 Provider 包裹,可以使用 null 作为默认值
const ThemeContext = createContext<ThemeContextValue | null>(null);
// 消费时强制判断
function SomeComponent() {
const value = useContext(ThemeContext);
if (value === null) {
throw new Error("SomeComponent 必须在 ThemeProvider 内部使用");
}
return <div>当前主题:{value.theme}</div>;
}
2.2 配置 Provider 共享状态
Provider 基础用法
Context.Provider 是一个特殊的 React 组件,它的 value 属性就是你要共享给后代组件的数据。
import { useState } from "react";
import ThemeContext from "./ThemeContext";
function ThemeProvider({ children }: { children: React.ReactNode }) {
// 使用 useState 管理共享状态
const [theme, setTheme] = useState<"light" | "dark">("light");
// 把状态和操作方法一起放入 value
const value = {
theme,
toggleTheme: () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
},
};
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
export default ThemeProvider;
在根组件中包裹 Provider
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import ThemeProvider from "./ThemeProvider";
const root = ReactDOM.createRoot(document.getElementById("root")!);
root.render(
// React 19 严格模式下建议包裹 Provider 在应用顶层
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
);
使用 useReducer 管理更复杂的状态
当 Context 共享的状态逻辑变复杂时,推荐用 useReducer 替代 useState:
import { useReducer, createContext, ReactNode } from "react";
// 定义 State
type Theme = "light" | "dark";
interface ThemeState {
theme: Theme;
fontSize: "small" | "medium" | "large";
}
// 定义 Action 类型
type ThemeAction =
| { type: "TOGGLE_THEME" }
| { type: "SET_FONT_SIZE"; payload: ThemeState["fontSize"] };
// reducer 函数
function themeReducer(state: ThemeState, action: ThemeAction): ThemeState {
switch (action.type) {
case "TOGGLE_THEME":
return { ...state, theme: state.theme === "light" ? "dark" : "light" };
case "SET_FONT_SIZE":
return { ...state, fontSize: action.payload };
default:
return state;
}
}
interface ThemeContextValue {
state: ThemeState;
dispatch: React.Dispatch<ThemeAction>;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
function ThemeProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(themeReducer, {
theme: "light",
fontSize: "medium",
});
return (
<ThemeContext.Provider value={{ state, dispatch }}>
{children}
</ThemeContext.Provider>
);
}
export { ThemeContext, ThemeProvider };
React 19 中 Provider 的渲染优化规则
在 React 19 中,Context Provider 的 value 仍然遵循引用相等的比较规则:
- 如果 Provider 的
value引用发生变化,所有订阅该 Context 的后代组件都会重新渲染 - 如果
value引用不变,即使内部某个属性变化,后代组件也不会重渲染(因为 React 只比较value的整体引用)
这意味着下面这种写法会导致每次 Provider 渲染都触发所有消费者重渲染:
// ❌ 错误:每次渲染都创建新对象
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider
value={{
theme,
toggleTheme: () => setTheme((t) => (t === "light" ? "dark" : "light")),
}}
>
{children}
</ThemeContext.Provider>
);
}
✅ 正确写法:用 useMemo 缓存 value
import { useState, useMemo, ReactNode } from "react";
import ThemeContext from "./ThemeContext";
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<"light" | "dark">("light");
// 使用 useMemo 缓存 value 对象,只有当 theme 变化时才创建新对象
const value = useMemo(
() => ({
theme,
toggleTheme: () => setTheme((t) => (t === "light" ? "dark" : "light")),
}),
[theme],
);
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
export default ThemeProvider;
React 19 说明:虽然 React 19 引入了更智能的编译优化,但 Context 的
value比较仍然基于引用相等。对于高频使用的 Context,务必用useMemo缓存value。
2.3 三种消费 Context 的方式
React 提供了三种消费 Context 的方式,分别适用于不同场景。
方式一:useContext Hook(最常用,函数组件首选)
import { useContext } from "react";
import ThemeContext from "./ThemeContext";
function ThemeButton() {
// 直接读取 Context 中的 theme 和 toggleTheme
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === "dark" ? "#333" : "#fff",
color: theme === "dark" ? "#fff" : "#333",
}}
>
切换到 {theme === "dark" ? "浅色" : "深色"} 模式
</button>
);
}
export default ThemeButton;
适用场景:函数组件、代码简洁、现代 React 开发的标准写法。
方式二:Context.Consumer(类组件或渲染属性模式)
import ThemeContext from "./ThemeContext";
function ThemePanel() {
return (
<ThemeContext.Consumer>
{(value) => (
<div
style={{
backgroundColor: value.theme === "dark" ? "#222" : "#f5f5f5",
color: value.theme === "dark" ? "#fff" : "#000",
padding: 20,
}}
>
<h3>当前主题:{value.theme}</h3>
<button onClick={value.toggleTheme}>切换主题</button>
</div>
)}
</ThemeContext.Consumer>
);
}
export default ThemePanel;
适用场景:
- 类组件(不想用
contextType的静态属性) - 需要在 JSX 中根据 Context 值进行条件渲染的片段
- 渲染属性(render prop)模式遗留代码
方式三:contextType(类组件专用,限制较多)
import { Component } from "react";
import ThemeContext from "./ThemeContext";
class ThemeDisplay extends Component {
// 在类组件中声明 contextType
static contextType = ThemeContext;
render() {
// 通过 this.context 访问
const { theme } = this.context;
return (
<div>
类组件读取到的主题:<strong>{theme}</strong>
</div>
);
}
}
export default ThemeDisplay;
限制:
- 一个类组件只能声明一个
contextType - 如果组件需要消费多个 Context,无法使用
contextType - 只能访问 Context,不能监听多个 Context
三种方式对比总结
| 消费方式 | 适用组件 | 可消费多个 Context | 代码简洁度 | 推荐度 |
|---|---|---|---|---|
useContext |
函数组件 | ✅ 可以 | 高 | ⭐⭐⭐ 首选 |
Context.Consumer |
函数组件/类组件 | ✅ 可以 | 中 | ⭐⭐ 兼容旧代码 |
contextType |
类组件 | ❌ 只能一个 | 低 | ⭐ 不推荐新项目 |
2.4 React 19 中的 Context 新特性
React 19 在 Context 方面没有翻天覆地的 API 变化,但有几个值得教学和开发关注的点:
1. Context 与新的 Hooks 体系兼容
React 19 引入了 use、useActionState、useFormStatus 等新 Hooks,Context 与这些新 Hook 可以自然组合使用。例如用 use 读取 Context 在 Suspense 场景下更友好:
import { use, Suspense } from "react";
import ThemeContext from "./ThemeContext";
function ThemeAwareContent() {
// use 可以读取 Context(在 React 19 中可用)
const { theme } = use(ThemeContext);
return <div>当前主题:{theme}</div>;
}
function App() {
return (
<ThemeProvider>
<Suspense fallback={<div>加载中...</div>}>
<ThemeAwareContent />
</Suspense>
</ThemeProvider>
);
}
注意:
use目前仍处于逐步推广阶段,函数组件中useContext仍然是主流写法。
2. Provider 与渲染优化的边界
React 19 的编译器(React Compiler)可以自动缓存部分依赖,但 Context 的 value 仍然是引用比较。教学时需要反复强调:
- 不要把 Context 当作"全局状态管理库"随意塞入高频变化的数据
- 务必用
useMemo包裹value - 考虑把高频变化的数据放到更细粒度的 Context 中,或使用外部状态管理库
3. Server Components 与 Context 的兼容性
React 19 强化了 Server Components 能力。需要注意:
- Context 是客户端概念,在 Server Component 中不能使用
useContext - 如果需要在 Server Component 中共享数据,应通过 props 传递
- Context Provider 本身应放在客户端组件树中
// 客户端组件(Client Component)中才能使用 Context
"use client";
import { ThemeProvider } from "./ThemeProvider";
export function ClientRoot({ children }: { children: React.ReactNode }) {
return <ThemeProvider>{children}</ThemeProvider>;
}
4. 与旧版 React 的差异
| 特性 | React 18 及以前 | React 19 |
|---|---|---|
createContext |
支持 | 支持,行为一致 |
useContext |
支持 | 支持,推荐方式 |
use 读取 Context |
不支持 | 支持 |
| Provider 引用比较 | 引用比较 | 引用比较(未改变) |
| Server Components | 实验性 | 更成熟,Context 仍仅限客户端 |
三、新手常见错误与正确写法
3.1 错误:直接修改 Context value
❌ 错误示例
function BadComponent() {
const { theme, setTheme } = useContext(ThemeContext);
const handleClick = () => {
// ❌ 错误:直接修改 Context 中的值
theme = "dark"; // 这不会触发任何更新,且 TypeScript 可能报错
};
return <button onClick={handleClick}>切换</button>;
}
错误原因:Context 的值是只读的共享引用。直接修改它既不会触发 React 重新渲染,也会破坏单向数据流。
✅ 正确写法
function GoodComponent() {
const { theme, toggleTheme } = useContext(ThemeContext);
const handleClick = () => {
// ✅ 正确:通过 Provider 提供的函数来修改状态
toggleTheme();
};
return <button onClick={handleClick}>当前主题:{theme}</button>;
}
3.2 错误:Provider 嵌套层级错误导致取值异常
❌ 错误示例
function App() {
return (
<div>
<UserProvider>
<Header />
</UserProvider>
{/* 错误:Main 在 UserProvider 外部,无法消费 user */}
<Main />
</div>
);
}
function Main() {
const { user } = useContext(UserContext); // 得到的是默认值 null
return <div>{user.name}</div>; // 报错!Cannot read properties of null
}
✅ 正确写法
function App() {
return (
<UserProvider>
<div>
<Header />
<Main /> {/* ✅ Main 在 Provider 内部 */}
<Footer />
</div>
</UserProvider>
);
}
function Main() {
const { user } = useContext(UserContext);
return <div>{user.name}</div>;
}
调试技巧:如果消费到的是默认值,首先检查组件是否真的被对应的 Provider 包裹。
3.3 错误:useContext 依赖错误导致重复渲染
❌ 错误示例
function UserProfile() {
const user = useContext(UserContext); // 把整个 user 对象取出来
// 假设 UserProfile 只关心 username
return <div>用户名:{user.username}</div>;
}
上面的代码本身没问题,但如果 Provider 的 value 每次都创建新对象,即使 username 没变,也会引起 UserProfile 重渲染。
✅ 正确写法:拆分细粒度 Context
// 把用户信息拆成两个 Context
const UserIdentityContext = createContext(null); // 包含 username, id
const UserPreferencesContext = createContext(null); // 包含 theme, language
function UserProfile() {
// 只消费自己关心的 Context
const { username } = useContext(UserIdentityContext);
return <div>用户名:{username}</div>;
}
function SettingsPanel() {
// 只消费偏好设置 Context
const { language } = useContext(UserPreferencesContext);
return <div>语言:{language}</div>;
}
3.4 错误:Provider 的 value 没有使用 useMemo
// ❌ 每次 ThemeProvider 渲染都会创建新对象,导致所有消费者重渲染
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
✅ 正确写法
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
四、注意事项
4.1 value 属性引用变化导致的不必要重渲染
问题:Provider 的 value 每次渲染都创建新对象,导致所有消费者都重新渲染。
解决方案:
import { useMemo, useState } from "react";
import UserContext from "./UserContext";
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [isLogin, setIsLogin] = useState(false);
// 使用 useMemo 缓存 value
const value = useMemo(
() => ({
user,
isLogin,
setUser,
setIsLogin,
}),
[user, isLogin],
);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
4.2 拆分多个细粒度 Context
问题:把所有共享数据塞进一个 Context,导致任何小变化都引起全局重渲染。
方案:按关注点拆分。
// AuthContext.tsx
const AuthContext = createContext(null);
// ThemeContext.tsx
const ThemeContext = createContext(null);
// LocaleContext.tsx
const LocaleContext = createContext(null);
// 组合 Provider
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<LocaleProvider>{children}</LocaleProvider>
</ThemeProvider>
</AuthProvider>
);
}
4.3 避免过度使用 Context
| 场景 | 推荐方案 |
|---|---|
| 父子两层传递 | props |
| 需要共享但变化不频繁 | Context |
| 高频变化或复杂状态 | Zustand / Jotai / Redux Toolkit |
| 组件内部状态 | useState / useReducer |
4.4 Context 默认值的调试技巧
// 开发环境下打印默认值命中情况
function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
console.warn("useTheme 必须在 ThemeProvider 内部使用");
throw new Error("useTheme 必须在 ThemeProvider 内部使用");
}
return context;
}
4.5 生产环境下的类型安全检查
// 推荐封装一个自定义 Hook,集中做类型检查
import { useContext } from "react";
import ThemeContext from "./ThemeContext";
export function useTheme() {
const context = useContext(ThemeContext);
if (context === null || context === undefined) {
throw new Error("useTheme 必须在 ThemeProvider 内部使用");
}
return context;
}
4.6 动态创建 Context 的注意事项
// ❌ 错误:在组件内部动态创建 Context
function App() {
const MyContext = createContext(null); // 每次渲染都会创建新 Context
return ...
}
// ✅ 正确:在模块顶层创建 Context
const MyContext = createContext(null);
function App() {
return ...
}
4.7 多级 Provider 嵌套时的值覆盖规则
Context 遵循"就近原则":组件会消费最近的匹配 Provider 的 value。
function App() {
return (
<ThemeProvider theme="light">
<Parent />
</ThemeProvider>
);
}
function Parent() {
return (
<ThemeProvider theme="dark">
<Child /> {/* Child 消费到的是 dark */}
</ThemeProvider>
);
}
function Child() {
const { theme } = useTheme();
return <div>当前主题:{theme}</div>; // 输出:dark
}
五、实战案例:主题切换系统
案例目标
实现一个完整的主题切换功能:
- 创建
ThemeContext - 在根组件配置
ThemeProvider - 在嵌套 3 层的子组件中消费并切换主题
项目结构
src/
├── contexts/
│ ├── ThemeContext.tsx
│ └── ThemeProvider.tsx
├── components/
│ ├── Header.tsx
│ ├── NavBar.tsx
│ └── ThemeToggleButton.tsx
├── hooks/
│ └── useTheme.ts
├── App.tsx
└── main.tsx
步骤 1:创建 ThemeContext
// src/contexts/ThemeContext.tsx
import { createContext } from "react";
export type Theme = "light" | "dark";
export interface ThemeContextValue {
theme: Theme;
toggleTheme: () => void;
}
// 使用 null 作为默认值,强制消费者必须被 Provider 包裹
export const ThemeContext = createContext<ThemeContextValue | null>(null);
步骤 2:创建 ThemeProvider
// src/contexts/ThemeProvider.tsx
import { useState, useMemo, ReactNode } from "react";
import { ThemeContext, Theme } from "./ThemeContext";
interface ThemeProviderProps {
children: ReactNode;
defaultTheme?: Theme;
}
export function ThemeProvider({
children,
defaultTheme = "light",
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(defaultTheme);
// 使用 useMemo 缓存 value,避免不必要的重渲染
const value = useMemo(
() => ({
theme,
toggleTheme: () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
},
}),
[theme],
);
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
步骤 3:封装 useTheme Hook
// src/hooks/useTheme.ts
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
export function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error("useTheme 必须在 ThemeProvider 内部使用");
}
return context;
}
步骤 4:在 main.tsx 中包裹 Provider
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ThemeProvider } from "./contexts/ThemeProvider";
const root = ReactDOM.createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
);
步骤 5:在 App 中嵌套 3 层组件
// src/App.tsx
import Header from "./components/Header";
function App() {
return (
<div className="app">
<Header />
</div>
);
}
export default App;
// src/components/Header.tsx
import NavBar from "./NavBar";
export default function Header() {
return (
<header className="header">
<h1>我的应用</h1>
<NavBar />
</header>
);
}
// src/components/NavBar.tsx
import ThemeToggleButton from "./ThemeToggleButton";
export default function NavBar() {
return (
<nav className="nav">
<a href="#">首页</a>
<a href="#">关于</a>
<ThemeToggleButton />
</nav>
);
}
步骤 6:在底层组件消费并切换主题
// src/components/ThemeToggleButton.tsx
import { useTheme } from "../hooks/useTheme";
export default function ThemeToggleButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
className={`theme-toggle ${theme}`}
onClick={toggleTheme}
style={{
backgroundColor: theme === "dark" ? "#fff" : "#333",
color: theme === "dark" ? "#333" : "#fff",
padding: "8px 16px",
border: "none",
borderRadius: 4,
cursor: "pointer",
}}
>
{theme === "dark" ? "☀️ 切换到浅色" : "🌙 切换到深色"}
</button>
);
}
步骤 7:主题样式应用(可选)
// src/App.tsx
import { useTheme } from "./hooks/useTheme";
import Header from "./components/Header";
function App() {
const { theme } = useTheme();
return (
<div
className="app"
style={{
minHeight: "100vh",
backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff",
color: theme === "dark" ? "#ffffff" : "#1a1a1a",
transition: "all 0.3s ease",
}}
>
<Header />
<main style={{ padding: 20 }}>
<p>当前主题:{theme}</p>
</main>
</div>
);
}
export default App;
数据流图解
main.tsx
└── ThemeProvider (value: { theme, toggleTheme })
└── App
└── Header
└── NavBar
└── ThemeToggleButton
└── useTheme() → 读取/切换 theme
数据从
ThemeProvider向下广播,经过App→Header→NavBar,最终到达ThemeToggleButton。中间组件完全不需要传递任何 props。
六、课后思考题
思考题 1
在 ThemeProvider 中,如果不使用 useMemo 包裹 value,当 ThemeProvider 的父组件重新渲染时,会发生什么?请解释原因。
思考题 2
如果一个类组件需要同时消费 ThemeContext 和 UserContext,能否使用 contextType?如果不能,应该使用哪种方式?
思考题 3
在实际项目中,如果用户偏好设置(语言、字体大小)会频繁变化,而用户身份信息(用户名、角色)很少变化,你会如何组织 Context?请说明理由。
思考题 4
假设 ThemeProvider 被嵌套在另一个 ThemeProvider 内部,内层的 value 会覆盖外层的 value。请举一个实际业务中需要这种嵌套 Provider 的场景。
思考题 5
在 React 19 的 Server Component 中,可以直接使用 useContext 吗?如果不能,正确的做法是什么?
实践作业
请在本案例基础上,扩展实现以下功能:
- 在
ThemeContext中增加fontSize字段(small|medium|large),并通过ThemeProvider提供修改方法 - 创建
FontSizeSelector组件,可以在NavBar中切换字体大小 - 使用
useMemo优化ThemeProvider的value - 在
useTheme中增加类型安全校验,确保组件必须被 Provider 包裹
总结:Context 是 React 解决跨组件数据共享的核心工具。掌握它,需要记住三个关键点:
- Context 适用于共享且不频繁变化的数据(主题、语言、用户信息)
- Provider 的 value 要用 useMemo 缓存,避免无意义的重渲染
- 能用 props 解决的简单场景不要滥用 Context,保持组件的清晰和可复用性
在 React 19 中,Context 的核心 API 保持稳定,但与新的 Hooks 和 Server Components 的协同需要额外留意。希望本教程能帮助你建立起正确、系统的 Context 使用认知。