React Context 详解:从 Props 传递到跨组件状态共享

React Context 详解:从 Props 传递到跨组件状态共享

学习目标:理解 React Context 的核心原理,掌握 createContext、Provider、useContext,并能在真实业务项目中使用。

学习建议:Context 属于 React 的重要基础知识,重点掌握实际使用方式,不需要深入研究底层源码。

一、Context 是什么?

React Context 是 React 提供的一种跨组件层级传递数据的机制。

正常情况下,React 组件之间通过 Props 传递数据。

例如:

css 复制代码
App
 └── Layout
      └── Header
           └── UserInfo

假设 UserInfo 组件需要获取当前登录用户的信息。

如果使用 Props,就需要把 user 一层层传递下去:

javascript 复制代码
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。

scss 复制代码
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:

javascript 复制代码
import { createContext } from "react";

export const UserContext = createContext(null);

这里:

scss 复制代码
createContext(null)

表示创建一个 Context,默认值是 null。

默认值只会在组件上方没有匹配的 Provider 时使用。

第二步:通过 Provider 提供数据

javascript 复制代码
import { UserContext } from "./UserContext";
import Layout from "./Layout";

function App() {
  const user = {
    name: "Kevin",
    role: "admin"
  };

  return (
    <UserContext.Provider value={user}>
      <Layout />
    </UserContext.Provider>
  );
}

核心代码:

xml 复制代码
<UserContext.Provider value={user}>
  <Layout />
</UserContext.Provider>

含义是:让 Layout 以及它的后代组件能够获取 user。

React 19 也支持直接使用 <UserContext value={user}> 提供数据;这里使用兼容 React 18 的经典 Provider 写法。

第三步:useContext 读取数据

javascript 复制代码
import { useContext } from "react";
import { UserContext } from "./UserContext";

function UserInfo() {
  const user = useContext(UserContext);

  return (
    <div>
      当前用户:{user?.name ?? "未登录"}
    </div>
  );
}

这里:

ini 复制代码
const user = useContext(UserContext);

意思是:

从当前组件上方最近的 UserContext.Provider 中读取 value。

即使中间隔了很多层组件,也不需要逐层传递。


四、实战:Context + useState 实现主题切换

前面只演示了读取固定数据。

在实际项目中,Context 经常和 useState 配合,实现可以动态更新的共享状态。

例如多个组件需要共享深色、浅色主题。

1. 创建 ThemeContext

javascript 复制代码
// 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

javascript 复制代码
import { ThemeProvider } from "./ThemeContext";
import Header from "./Header";
import Content from "./Content";

function App() {
  return (
    <ThemeProvider>
      <Header />
      <Content />
    </ThemeProvider>
  );
}

3. 在不同组件中读取和修改状态

Header.jsx:

javascript 复制代码
import { useTheme } from "./ThemeContext";

function Header() {
  const { theme, toggleTheme } = useTheme();

  return (
    <header>
      当前主题:{theme}

      <button onClick={toggleTheme}>
        切换主题
      </button>
    </header>
  );
}

export default Header;

Content.jsx:

ini 复制代码
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 更新后为什么组件会变化?

理解这个流程即可:

scss 复制代码
用户点击切换主题
        ↓
调用 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

例如:

xml 复制代码
<AppContext.Provider
  value={{
    user,
    theme,
    tasks,
    filters,
    selectedTask,
    loading
  }}
>
  <App />
</AppContext.Provider>

如果这些值频繁发生变化,所有读取该 Context 的组件都有可能因为 value 变化而重新渲染。

更合理的方式是按照职责拆分:

复制代码
UserContext
ThemeContext
TaskContext

Context 不支持像 Zustand 一样直接通过 selector 对单个字段进行细粒度订阅。

2. Provider 必须位于消费者上方

xml 复制代码
<UserContext.Provider value={user}>
  <UserInfo />
</UserContext.Provider>

这样 UserInfo 才能读取这个 Provider 提供的值。

如果组件上方没有匹配的 Provider,就会读取 createContext 指定的默认值。

3. 不要滥用 Context

如果两个组件只是简单的父子关系:

ini 复制代码
<TaskCard task={task} />

直接使用 Props 往往更清晰。

没有必要为了传递一个参数,就专门创建一个 Context。


八、Context、Props、useState 怎么选择?

可以直接按照下面的规则判断。

场景 推荐方式
输入框内容变化 useState
父组件传任务给子组件 Props
多层组件共享用户信息 Context
全局主题切换 Context + useState
多模块复杂共享状态 Zustand
后端任务列表请求与缓存 SWR / TanStack Query

实际工作中的原则:

状态能放在局部,就尽量放在局部;需要跨层级共享,再考虑 Context;共享状态复杂时,再考虑 Zustand。


九、React Context 学习总结

你只需要记住以下三个 API:

csharp 复制代码
// 1. 创建 Context
const UserContext = createContext(null);
xml 复制代码
// 2. 提供数据
<UserContext.Provider value={user}>
  <App />
</UserContext.Provider>
ini 复制代码
// 3. 读取数据
const user = useContext(UserContext);

再记住三个核心区别:

  1. Props:适合父子组件之间传递数据。
  2. Context:适合跨组件层级传递共享数据。
  3. Zustand:适合管理复杂的共享客户端状态。

对于 React + TypeScript 中后台开发,Context 学到这里就基本够用了。

最重要的不是熟记 API,而是能够判断:这个状态应该放在组件内部、通过 Props 传递,还是放进 Context / Zustand 共享。

相关推荐
Penge66624 分钟前
React 事件处理:bind(this) 原理与实际开发常用写法
前端
ZhengEnCi35 分钟前
小白用 AI 做官网完全指南:不用框架的六格清单,双击就能打开
前端·html·ai编程
zhanlang02171 小时前
HTML 零基础入门到企业实战【第五章 5.2】图片路径精讲:绝对路径、相对路径、网络路径(新手必踩坑点)
前端·html
夕水1 小时前
我用trae重构了ew-color-picker,一个插件化架构的现代颜色选择器
前端·javascript·开源
Penge6661 小时前
React JSX 核心语法:从入门到业务开发实战
前端
zhanlang02171 小时前
HTML 零基础入门到企业实战全套教程【第五章 5.5 综合案例:图文影音展示页面开发】
前端·html
zhanlang02172 小时前
HTML 零基础入门到企业实战全套教程【连载 5.3】audio、video 音频视频标签|兼容格式、自动播放踩坑、企业规范
前端·html
qq_406981033 小时前
c++ lamda(三)Lambda表达式
java·前端·c++
zhanlang02173 小时前
HTML 零基础入门到企业实战【第五章 5.4】内嵌网页 iframe 标签:嵌入网页、视频、地图实战
前端·html·音视频