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

0 阅读6分钟

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,这里可以对比一下。

对比ContextZustand
是否 React 内置是否,需要安装
核心作用跨层级传递数据管理共享客户端状态
是否需要 Provider通常需要常规使用不需要
状态更新通常配合 useStateStore 内置 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);

再记住三个核心区别:

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

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

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