一、useContext 是什么?
useContext 是 React 提供的一个内置 Hook,用于在函数组件中便捷地访问 Context(上下文)中的数据,避免了“props 层层传递”(也叫“props 钻透”/props drilling)的问题。
可以把 Context 理解为 React 组件树中的“全局变量”(但作用域可自定义),而 useContext 就是组件获取这个“全局变量”的钥匙。
二、为什么需要 useContext?
先看一个痛点场景:如果父组件的某个数据需要传递给深层嵌套的子组件,传统方式需要逐层通过 props 传递,即使中间组件用不到这个数据,也必须接收并转发,代码冗余且维护性差。
// 痛点示例:props 层层传递
function Grandparent() {
const theme = "dark";
return <Parent theme={theme} />;
}
// 中间组件 Parent 用不到 theme,但必须转发
function Parent({ theme }) {
return <Child theme={theme} />;
}
// 最终需要 theme 的子组件
function Child({ theme }) {
return <div>当前主题:{theme}</div>;
}
而 useContext + Context 可以让 Child 直接跳过中间组件,获取 Grandparent 中的数据,大幅简化代码。
三、useContext 的核心使用步骤
使用 useContext 必须配合 React.createContext() 创建的 Context 对象,完整流程分为 3 步:
步骤 1:创建 Context 对象
首先用 createContext 创建一个 Context,可传入默认值(仅在组件找不到对应的 Provider 时生效)。
import { createContext } from 'react';
// 创建主题 Context,默认值为 light
const ThemeContext = createContext('light');
步骤 2:用 Provider 提供数据
通过 Context 对象的 Provider 组件,为其包裹的所有子组件提供数据(value 属性是要传递的数据)。
import { createContext } from 'react';
const ThemeContext = createContext('light');
function Grandparent() {
const theme = "dark";
// Provider 包裹子组件,value 传递数据
return (
<ThemeContext.Provider value={theme}>
<Parent /> {/* 中间组件无需接收 theme */}
</ThemeContext.Provider>
);
}
// 中间组件 Parent 无需处理 theme
function Parent() {
return <Child />;
}
步骤 3:用 useContext 消费数据
在需要数据的子组件中,调用 useContext 并传入创建的 Context 对象,即可直接获取 Provider 传递的 value。
import { useContext } from 'react';
import { ThemeContext } from './Grandparent';
function Child() {
// 核心:用 useContext 获取 Context 中的数据
const theme = useContext(ThemeContext);
return <div style={{ background: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#333' }}>
当前主题:{theme}
</div>;
}
四、进阶用法:传递复杂数据 + 更新逻辑
实际开发中,Context 不仅会传递静态数据,还会传递状态和修改状态的方法,让子组件能更新 Context 中的数据。
import { createContext, useContext, useState } from 'react';
// 1. 创建 Context(默认值可设为对象,保持结构统一)
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {} // 空函数占位
});
// 2. 封装 Provider 组件(方便复用)
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>
);
}
// 3. 封装自定义 Hook(简化消费逻辑)
export function useTheme() {
return useContext(ThemeContext);
}
// 4. 子组件使用
function ToggleButton() {
const { theme, toggleTheme } = useTheme(); // 直接解构使用
return <button onClick={toggleTheme}>
切换主题(当前:{theme})
</button>;
}
// 根组件使用 Provider 包裹
function App() {
return (
<ThemeProvider>
<ToggleButton />
<Child />
</ThemeProvider>
);
}
五、useContext存在的问题以及如何优化
一、useContext 性能问题的核心原因
useContext 的核心问题是:当 Context 的 Provider 值发生变化时,所有消费这个 Context 的组件都会无条件重渲染,哪怕组件只用到了 Context 中的一小部分数据。
二、具体优化方案
1. 拆分 Context(最核心的优化)
将大的 Context 拆分为多个小的、职责单一的 Context,让组件只消费自己需要的 Context,避免因无关数据更新导致重渲染。
示例:
import React, { createContext, useContext, useState } from 'react';
// 拆分前:一个包含所有数据的大 Context
const BigContext = createContext();
// 拆分后:多个小 Context
const UserContext = createContext(); // 仅存储用户信息
const ThemeContext = createContext(); // 仅存储主题信息
const CartContext = createContext(); // 仅存储购物车信息
// 拆分后的 Provider 组件
const AppProvider = ({ children }) => {
const [user, setUser] = useState({ name: '张三', age: 20 });
const [theme, setTheme] = useState('light');
const [cart, setCart] = useState([]);
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<CartContext.Provider value={{ cart, setCart }}>
{children}
</CartContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
};
// 消费组件:只用到 ThemeContext,仅当 theme 变化时重渲染
const ThemeButton = () => {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换{theme}主题
</button>
);
};
// 消费组件:只用到 UserContext,仅当 user 变化时重渲染
const UserInfo = () => {
const { user } = useContext(UserContext);
return <div>用户名:{user.name}</div>;
};
2. 稳定 Context 的 value(避免不必要的引用更新)
如果 Context 的 value 是一个对象/函数,每次渲染时会创建新的引用,即使数据没变化,也会触发消费组件重渲染。解决方法:
- 用
useMemo缓存对象类型的value; - 用
useCallback缓存函数类型的value。
示例:
import React, { createContext, useContext, useState, useMemo, useCallback } from 'react';
const UserContext = createContext();
const UserProvider = ({ children }) => {
const [user, setUser] = useState({ name: '张三', age: 20 });
// 缓存更新用户的函数(避免每次渲染创建新函数)
const updateUserName = useCallback((newName) => {
setUser(prev => ({ ...prev, name: newName }));
}, []);
// 缓存 Context 的 value(仅当 user 或 updateUserName 变化时更新)
const contextValue = useMemo(() => ({
user,
updateUserName
}), [user, updateUserName]);
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
};
总结
useContext的核心作用是解决 props 层层传递问题,让组件直接访问上级 Context 中的数据;- 使用流程:
createContext创建上下文 →Provider提供数据 →useContext消费数据; - useContext存在一定性能问题可以通过以下几点优化:
- 拆分 Context:将大 Context 拆分为多个小 Context,组件只消费所需的部分,从根源减少重渲染;
- 稳定 value 引用:用
useMemo/useCallback缓存 Context 的value,避免无意义的引用更新触发重渲染; - 这些方案的核心目标都是让组件只在真正依赖的数据变化时才重渲染,最大化利用 React 的渲染机制,提升
useContext使用的性能。