在开发 React 应用时,你是否遇到过这样的烦恼?
有些全局数据(比如主题色、用户信息、语言设置等)需要在很多组件中使用,但这些组件层级很深,每次都要通过 props 一层层传递,代码变得冗长又难以维护。所以有没有一种方式,可以让我们在任意组件中直接获取这些全局数据,而不用"层层传递"呢?此时我们就需要React 的 Context 和 useContext Hook 了。
什么是 Context?
Context 是 React 提供的全局数据共享方案,可以让数据在组件树中"跨层级"传递,避免多层 props 传递(props drilling)的问题。常用于主题、用户信息、语言等全局状态。
它的核心思想是:
将需要共享的数据放在 Context 中,通过 Provider 提供数据,子组件通过 Consumer 或 useContext 获取数据。
而 Context 由以下几部分组成:
1.React.createContext()
创建 Context 对象
2.Context.Provider
提供数据
3.Context.Consumer
或 useContext(Context)
消费数据
其中常见的应用场景有:
- 主题切换(如深色/浅色模式)
- 用户登录信息
- 多语言(国际化)
- 权限控制
- 配置项共享
为什么需要 useContext?
解决 props drilling 问题
传统做法通常为,父组件通过 props
一层层把数据传递给子组件,层级深时会很繁琐,且中间组件明明不需要用到数据却要负责"中转"。
比如下面这段代码:
jsx
// 爷爷组件
function Grandfather() {
// 爷爷要给孙子的东西:一个玩具车
const gift = "玩具车";
return <Father gift={gift} />;
}
// 爸爸组件(中间传递者,自己不用这个礼物)
function Father({ gift }) {
return <You gift={gift} />;
}
// 你(中间传递者,自己不用这个礼物)
function You({ gift }) {
return <Child gift={gift} />;
}
// 孙子组件(最终接收礼物的人)
function Child({ gift }) {
return <p>孙子收到了:{gift}</p>;
}
// 渲染整个家庭结构
function App() {
return <Grandfather />;
}
-
首先爷爷组件(Grandfather)准备好要给孙子的 "玩具车",然后必须先把礼物传给爸爸组件(Father);
-
爸爸自己也用不上这个玩具车,却得继续把礼物传给 "你" 组件(You);
-
而"你" 同样不需要这个礼物,还是得再传给孙子组件(Child);
-
最后只有孙子组件真正用到了这个礼物,页面上会显示 "孙子收到了:玩具车"。
在整个过程中,爸爸和 "你" 就像 "工具人",只是负责传递,完全用不上礼物,却必须参与其中,这就是前面说的 "props drilling" 问题 ------ 数据得层层传递,中间环节完全是多余的中转。
Context 的基本用法
1. 创建 Context
jsx
import React from "react";
// 创建一个 Context 对象,可以传入默认值
const ThemeContext = React.createContext("light");
ThemeContext
是一个对象,包含 Provider 和 Consumer 两个组件。- 默认值只有在组件树中没有 Provider 时才会生效。
2. 提供 Context(Provider)
jsx
function App() {
const [theme, setTheme] = React.useState("dark");
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
切换主题
</button>
</ThemeContext.Provider>
);
}
- Provider 通过
value
属性向下传递数据 - Provider 可以嵌套,子 Provider 会覆盖父 Provider 的值
3. 消费 Context(useContext)
jsx
import { useContext } from "react";
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>当前主题:{theme}</div>;
}
useContext(Context)
直接获取最近的 Provider 的 value- 组件会在 Provider 的 value 变化时自动重新渲染
完整示例
jsx
import React, { createContext, useContext, useState } from "react";
// 1. 创建 Context
const ThemeContext = createContext("light");
// 2. 提供 Context
function App() {
const [theme, setTheme] = useState("dark");
return (
<ThemeContext.Provider value={theme}>
<Page />
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
切换主题
</button>
</ThemeContext.Provider>
);
}
// 3. 在任意子组件中消费 Context
function Page() {
return (
<div>
<Toolbar />
<Content />
</div>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>工具栏主题:{theme}</div>;
}
function Content() {
const theme = useContext(ThemeContext);
return <div>内容区主题:{theme}</div>;
}
export default App;
首先我们用createContext 创建了一个主题上下文,然后在顶层 App 组件中用 ThemeContext.Provider 提供当前主题值,并通过按钮切换主题;Page 组件下的 Toolbar 和 Content 子组件分别用 useContext 直接获取当前主题,实现了无需 props 传递即可在任意层级访问和响应主题变化
运行效果:
点击"切换主题"按钮,所有消费了 ThemeContext 的组件都会自动更新显示的主题。
useContext 的注意事项
-
只能在函数组件或自定义 Hook 中使用
不能在普通函数、类组件、条件语句、循环中调用。
-
只会订阅最近的 Provider
如果嵌套多个 Provider,会取离自己最近的那个。
jsx<ThemeContext.Provider value="dark"> <ThemeContext.Provider value="light"> <Toolbar /> {/* 这里获取到的 theme 是 "light" */} </ThemeContext.Provider> </ThemeContext.Provider>
-
Context 变化会导致所有消费它的组件重新渲染
如果 value 是对象或数组,建议用 useMemo 优化,避免不必要的渲染。
jsxconst value = useMemo(() => ({ theme, setTheme }), [theme]); <ThemeContext.Provider value={value}>...</ThemeContext.Provider>
-
Context 不是全局状态管理的万能钥匙
只适合少量全局数据,复杂场景建议用 Redux、MobX、Zustand 等专门的状态管理库。
进阶用法
1. 结合自定义 Hook 封装 Context
这样可以让消费方代码更简洁,隐藏实现细节。
jsx
// ThemeContext.tsx (使用 TypeScript 增强类型安全)
import { createContext, useContext, useState, PropsWithChildren } from "react";
// 定义主题类型
type ThemeType = "light" | "dark";
// 定义 Context 值的类型
interface ThemeContextValue {
theme: ThemeType;
setTheme: (theme: ThemeType) => void;
}
// 创建上下文,明确默认值类型
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
// 主题提供者组件
export function ThemeProvider({ children }: PropsWithChildren) {
// 状态管理逻辑
const [theme, setTheme] = useState<ThemeType>("dark");
// 将状态和方法组合成上下文值
const contextValue = {
theme,
setTheme,
};
return (
<ThemeContext.Provider value={contextValue}>
{children}
</ThemeContext.Provider>
);
}
// 自定义 Hook 获取主题上下文
export function useTheme() {
const context = useContext(ThemeContext);
// 运行时校验,确保在 Provider 内部使用
if (!context) {
throw new Error("useTheme 必须在 ThemeProvider 组件内部使用");
}
return context;
}
2. Context 传递对象和方法
Context 不仅可以传递基本类型,还可以传递对象、函数,实现更复杂的全局状态管理。
jsx
const UserContext = createContext();
function App() {
const [user, setUser] = useState({ name: "小明", loggedIn: false });
const login = (name) => setUser({ name, loggedIn: true });
const logout = () => setUser({ name: "", loggedIn: false });
return (
<UserContext.Provider value={{ user, login, logout }}>
<Profile />
</UserContext.Provider>
);
}
function Profile() {
const { user, login, logout } = useContext(UserContext);
return (
<div>
{user.loggedIn ? (
<>
<div>欢迎,{user.name}</div>
<button onClick={logout}>退出登录</button>
</>
) : (
<button onClick={() => login("小明")}>登录</button>
)}
</div>
);
}
这段代码的核心思路是通过 React Context API 实现全局状态管理,避免 props 层层传递:
首先我们创建 UserContext
作为数据传递的"管道";然后在根组件 App
中用 useState
管理用户状态(初始为未登录),并定义 login
和 logout
方法来修改状态,接着通过 UserContext.Provider
将用户状态和这两个方法作为 value
提供给所有子组件;最后,在需要使用用户状态的 Profile
组件中,通过 useContext(UserContext)
直接获取状态和方法,根据登录状态动态渲染欢迎信息或登录按钮,实现了跨层级的状态共享和组件通信。
3. 多个 Context 嵌套
可以同时使用多个 Context,分别管理不同的全局数据。
jsx
const ThemeContext = createContext();
const UserContext = createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<UserContext.Provider value={{ name: "小明" }}>
<Toolbar />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
const user = useContext(UserContext);
return (
<div>
主题:{theme},用户:{user.name}
</div>
);
}
核心思路是利用 React Context API 实现多层级状态的全局共享:
首先创建 ThemeContext
和 UserContext
两个独立的上下文容器,分别用于存储主题和用户信息;在根组件 App
中,通过嵌套的 Provider 组件分别为这两个上下文提供初始值(主题为 "dark",用户名为 "小明"),形成数据传递的层级结构;在需要使用这些数据的 Toolbar
组件中,通过调用两次 useContext
分别获取两个上下文中的值,无需通过中间组件层层传递,直接在任意层级的组件中获取所需的全局状态。
小结
useContext
让你在函数组件中轻松获取全局数据,避免繁琐的 props 传递。- 适合全局状态、主题、用户信息、国际化等场景。
- 推荐结合自定义 Hook 封装,提升可维护性和可读性。
- 注意性能优化和使用场景,避免滥用。