你是不是也遇到过这样的场景:深层嵌套的组件需要共享状态,不得不一层一层传递 props,代码像 "传话筒" 一样繁琐?这种 "props drilling"(props 透传)不仅让代码冗余,还让维护变得困难。而 React 的useContext
钩子,正是为解决这种跨层级状态共享而生。
为什么需要 useContext?从一个痛点场景说起
假设你有一个组件结构:App → Page → Child
,现在需要把App
中的theme
状态(主题)传递给最内层的Child
组件。如果用传统的 props 传递,代码会是这样:
jsx
// App组件
function App() {
const [theme, setTheme] = useState('light');
return <Page theme={theme} />; // 传参给Page
}
// Page组件
function Page({ theme }) {
return <Child theme={theme} />; // 透传给Child
}
// Child组件
function Child({ theme }) {
return <div>当前主题:{theme}</div>; // 最终使用
}
这种方式有两个明显问题:
- 冗余繁琐 :
Page
组件本身不需要theme
,却要被迫接收并传递,像个 "中间商"; - 难以维护:如果组件嵌套更深(比如 5 层以上),修改传递路径会牵一发而动全身。
而useContext
能让Child
组件直接 "跳过中间商",从App
那里获取theme
状态,彻底解决透传问题。
useContext 的核心:3 步实现跨层级状态共享
useContext
的使用流程非常固定,记住这三个步骤,就能轻松实现全局状态共享:
步骤 1:创建上下文对象(Context)
首先需要创建一个 "上下文对象",它就像一个 "状态容器",用于存储需要共享的数据。
jsx
// ThemeContext.js
import { createContext } from 'react';
// 创建上下文对象,参数是默认值(可选)
export const ThemeContext = createContext();
createContext
是 React 提供的创建上下文的方法;- 这个对象本身不存储数据,只是一个 "媒介",用于连接数据提供者和使用者。
步骤 2:用 Provider 提供共享状态
在顶层组件(如App
)中,使用上下文对象的Provider
组件包裹子组件树,通过value
属性提供需要共享的状态。
jsx
// App.jsx
import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import Page from './components/Page';
function App() {
const [theme, setTheme] = useState('light'); // 需要共享的状态
return (
// Provider包裹子组件,value传递共享数据
<ThemeContext.Provider value={theme}>
<Page />
{/* 切换主题的按钮 */}
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</ThemeContext.Provider>
);
}
ThemeContext.Provider
是上下文对象自带的组件;- 所有被它包裹的子组件(无论层级多深),都能获取到
value
中的数据; - 这里的
value
可以是任意类型(字符串、对象、函数等),通常会传递状态和修改状态的方法。
步骤 3:用 useContext 获取共享状态
在任意子组件中,通过useContext
钩子直接获取共享状态,无需通过 props 传递。
jsx
// Child.jsx
import { useContext } from 'react';
import { ThemeContext } from '../../ThemeContext';
const Child = () => {
// 直接获取上下文数据
const theme = useContext(ThemeContext);
return <div className={theme}>Child组件:当前主题{theme}</div>;
};
甚至可以在中间组件Page
中直接使用:
jsx
// Page.jsx
import { useContext } from 'react';
import { ThemeContext } from '../../ThemeContext';
import Child from '../Child';
const Page = () => {
const theme = useContext(ThemeContext);
return (
<div>
Page组件:当前主题{theme}
<Child />
</div>
);
};
效果 :App
中的theme
状态变化时,Page
和Child
组件会自动更新,显示最新的主题值。
进阶:用自定义 Hook 简化使用
如果多个组件都需要使用ThemeContext
,可以封装一个自定义 Hook,减少重复代码:
jsx
// hooks/useTheme.js
import { useContext } from 'react';
import { ThemeContext } from '../ThemeContext';
// 自定义Hook,封装useContext逻辑
export function useTheme() {
return useContext(ThemeContext);
}
之后在组件中直接调用这个 Hook:
jsx
// Page.jsx 中使用自定义Hook
import { useTheme } from '../../hooks/useTheme';
const Page = () => {
const theme = useTheme(); // 更简洁
return <div>Page组件:当前主题{theme}</div>;
};
这种方式的好处是:如果后续需要修改上下文逻辑(比如添加默认值判断),只需改useTheme
一处,所有使用它的组件都会生效。
useContext 的适用场景与注意事项
(适用场景)
- 跨层级状态共享:如主题设置、用户登录状态、语言切换等全局配置;
- 简化 props 传递:当组件嵌套超过 2 层,且需要共享数据时,比 props 更高效;
- 局部状态共享:不必全局使用,也可以在某个组件树中局部使用(如某个模块内部)。
(注意事项)
- 不要过度使用 :并非所有状态都需要用
useContext
,组件内部状态用useState
,父子组件用 props 更合适; - 性能考量 :当
Provider
的value
变化时,所有使用useContext
的组件都会重新渲染。如果value
是对象,建议用useMemo
缓存,避免不必要的重渲染; - 默认值的作用 :
createContext
的默认值只有在 "组件没有被对应 Provider 包裹" 时才会生效,平时主要靠Provider
的value
提供数据。