Context 深入解析
更新: 10/16/2025 字数: 0 字 时长: 0 分钟
什么是 Context
Context 提供了一种在组件树中传递数据的方式,无需通过 props 逐层传递。它解决了"prop drilling"(属性钻取)问题。
jsx
// 不使用 Context - 需要层层传递
function App() {
const [theme, setTheme] = useState('dark');
return <Layout theme={theme} />;
}
function Layout({ theme }) {
return <Content theme={theme} />;
}
function Content({ theme }) {
return <Button theme={theme} />;
}
function Button({ theme }) {
return <button className={theme}>Click</button>;
}
// 使用 Context - 直接访问
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Layout />
</ThemeContext.Provider>
);
}
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}Context API
1. createContext
jsx
const MyContext = createContext(defaultValue);
// defaultValue 只在没有匹配的 Provider 时使用
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const ConfigContext = createContext({ locale: 'zh-CN' });2. Context.Provider
jsx
<MyContext.Provider value={/* 共享的值 */}>
{children}
</MyContext.Provider>
// 示例
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Dashboard />
</UserContext.Provider>
);
}3. useContext Hook
jsx
const value = useContext(MyContext);
// 示例
function UserProfile() {
const { user, setUser } = useContext(UserContext);
if (!user) {
return <LoginForm onLogin={setUser} />;
}
return <div>Welcome, {user.name}</div>;
}4. Context.Consumer(旧式 API)
jsx
<MyContext.Consumer>
{value => /* 基于 context 值渲染 */}
</MyContext.Consumer>
// 示例(不推荐,使用 useContext 代替)
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
</ThemeContext.Consumer>Context 的实现原理
createContext 的实现
javascript
function createContext(defaultValue) {
const context = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue, // 用于并发模式
_threadCount: 0,
Provider: null,
Consumer: null,
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}Provider 的工作原理
javascript
// 简化的 Provider 实现
function updateContextProvider(
current,
workInProgress,
renderLanes
) {
const providerType = workInProgress.type;
const context = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = current !== null ? current.memoizedProps : null;
const newValue = newProps.value;
// 将新值推入栈
pushProvider(workInProgress, context, newValue);
// 如果值发生变化,需要标记消费者需要更新
if (oldProps !== null) {
const oldValue = oldProps.value;
if (Object.is(oldValue, newValue)) {
// 值没变,可以跳过
if (
oldProps.children === newProps.children
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes
);
}
} else {
// 值变了,标记消费者需要更新
propagateContextChange(
workInProgress,
context,
renderLanes
);
}
}
// 处理子节点
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
// 传播 Context 变化
function propagateContextChange(
workInProgress,
context,
renderLanes
) {
let fiber = workInProgress.child;
// 深度优先遍历子树
while (fiber !== null) {
let nextFiber;
// 检查当前 fiber 是否消费了这个 context
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
while (dependency !== null) {
// 如果消费了这个 context,标记需要更新
if (dependency.context === context) {
// 标记更新
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// 向上标记所有祖先
scheduleContextWorkOnParentPath(
fiber.return,
renderLanes,
workInProgress
);
// 标记依赖列表
list.lanes = mergeLanes(list.lanes, renderLanes);
break;
}
dependency = dependency.next;
}
}
// 继续遍历
fiber = nextFiber;
}
}useContext 的实现
javascript
function useContext(context) {
const dispatcher = resolveDispatcher();
return dispatcher.useContext(context);
}
// 读取 context
function readContext(context) {
const value = context._currentValue;
// 记录依赖
const contextItem = {
context: context,
next: null,
};
if (lastContextDependency === null) {
// 第一个依赖
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
};
} else {
// 添加到依赖链表
lastContextDependency = lastContextDependency.next = contextItem;
}
return value;
}Context 的性能问题
问题:Context 值变化会导致所有消费者重新渲染
jsx
const AppContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// ❌ 问题:任何状态变化都会导致所有消费者重新渲染
const value = { user, setUser, theme, setTheme };
return (
<AppContext.Provider value={value}>
<Header />
<Content />
<Footer />
</AppContext.Provider>
);
}
function Header() {
// 只使用 theme,但 user 变化时也会重新渲染
const { theme } = useContext(AppContext);
return <header className={theme}>Header</header>;
}性能优化方案
1. 分割 Context
jsx
// ✅ 方案 1:按功能分割 Context
const UserContext = createContext();
const ThemeContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Content />
<Footer />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
function Header() {
// 只订阅 theme,user 变化不会影响
const { theme } = useContext(ThemeContext);
return <header className={theme}>Header</header>;
}2. 使用 useMemo 缓存 value
jsx
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// ✅ 缓存 context value
const userValue = useMemo(
() => ({ user, setUser }),
[user]
);
const themeValue = useMemo(
() => ({ theme, setTheme }),
[theme]
);
return (
<UserContext.Provider value={userValue}>
<ThemeContext.Provider value={themeValue}>
<App />
</ThemeContext.Provider>
</UserContext.Provider>
);
}3. 拆分 Provider
jsx
// ✅ 方案 3:将 Provider 提取为独立组件
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(
() => ({ user, setUser }),
[user]
);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(
() => ({ theme, setTheme }),
[theme]
);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
function App() {
return (
<UserProvider>
<ThemeProvider>
<Layout />
</ThemeProvider>
</UserProvider>
);
}4. 使用 React.memo 阻止不必要的渲染
jsx
const Header = React.memo(function Header() {
const { theme } = useContext(ThemeContext);
return <header className={theme}>Header</header>;
});
const Content = React.memo(function Content() {
const { user } = useContext(UserContext);
return <main>{user?.name}</main>;
});5. 组件组合优化
jsx
// ✅ 使用 children 避免重新渲染
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function App() {
return (
<ThemeProvider>
{/* ExpensiveTree 不会因为 theme 变化而重新渲染 */}
<ExpensiveTree />
</ThemeProvider>
);
}6. 使用 Context Selector(第三方库)
jsx
import { createContext, useContextSelector } from 'use-context-selector';
const AppContext = createContext();
function App() {
const [state, setState] = useState({
user: null,
theme: 'light',
locale: 'zh-CN'
});
return (
<AppContext.Provider value={[state, setState]}>
<Header />
</AppContext.Provider>
);
}
function Header() {
// 只订阅 theme,其他属性变化不会触发重新渲染
const theme = useContextSelector(
AppContext,
([state]) => state.theme
);
return <header className={theme}>Header</header>;
}高级用法
1. 动态 Context
jsx
// 创建多个 Context 实例
function createThemeContext(defaultTheme) {
const ThemeContext = createContext(defaultTheme);
function ThemeProvider({ theme, children }) {
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
return { ThemeProvider, useTheme };
}
// 使用
const { ThemeProvider: LightThemeProvider, useTheme: useLightTheme } =
createThemeContext('light');
const { ThemeProvider: DarkThemeProvider, useTheme: useDarkTheme } =
createThemeContext('dark');2. Context 与 Reducer 结合
jsx
const StateContext = createContext();
const DispatchContext = createContext();
function reducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'SET_THEME':
return { ...state, theme: action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, {
user: null,
theme: 'light'
});
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 自定义 Hooks
function useAppState() {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error('useAppState must be used within AppProvider');
}
return context;
}
function useAppDispatch() {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error('useAppDispatch must be used within AppProvider');
}
return context;
}
// 使用
function UserProfile() {
const { user } = useAppState();
const dispatch = useAppDispatch();
const handleLogout = () => {
dispatch({ type: 'SET_USER', payload: null });
};
return <div>{user?.name}</div>;
}3. 嵌套 Provider
jsx
// 支持嵌套的 Theme Provider
function ThemeProvider({ theme, children }) {
const parentTheme = useContext(ThemeContext);
// 合并父级主题和当前主题
const mergedTheme = useMemo(
() => ({ ...parentTheme, ...theme }),
[parentTheme, theme]
);
return (
<ThemeContext.Provider value={mergedTheme}>
{children}
</ThemeContext.Provider>
);
}
// 使用
<ThemeProvider theme={{ primary: 'blue' }}>
<Header />
<ThemeProvider theme={{ secondary: 'red' }}>
<Content />
</ThemeProvider>
</ThemeProvider>4. Context 与 Portal
jsx
function Modal({ children }) {
const theme = useContext(ThemeContext);
// Portal 可以访问 Context
return ReactDOM.createPortal(
<div className={`modal ${theme}`}>
{children}
</div>,
document.body
);
}实际应用场景
1. 主题切换
jsx
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState(() => {
return localStorage.getItem('theme') || 'light';
});
const toggleTheme = useCallback(() => {
setTheme(prevTheme => {
const newTheme = prevTheme === 'light' ? 'dark' : 'light';
localStorage.setItem('theme', newTheme);
return newTheme;
});
}, []);
const value = useMemo(
() => ({ theme, toggleTheme }),
[theme, toggleTheme]
);
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// 使用
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
{theme === 'light' ? '🌙' : '☀️'}
</button>
);
}2. 国际化(i18n)
jsx
const I18nContext = createContext();
const translations = {
'zh-CN': {
welcome: '欢迎',
login: '登录',
},
'en-US': {
welcome: 'Welcome',
login: 'Login',
}
};
function I18nProvider({ children }) {
const [locale, setLocale] = useState('zh-CN');
const t = useCallback((key) => {
return translations[locale][key] || key;
}, [locale]);
const value = useMemo(
() => ({ locale, setLocale, t }),
[locale, t]
);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
function useI18n() {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useI18n must be used within I18nProvider');
}
return context;
}
// 使用
function LoginButton() {
const { t } = useI18n();
return <button>{t('login')}</button>;
}3. 用户认证
jsx
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 检查用户是否已登录
checkAuth().then(user => {
setUser(user);
setLoading(false);
});
}, []);
const login = useCallback(async (credentials) => {
const user = await loginAPI(credentials);
setUser(user);
localStorage.setItem('token', user.token);
}, []);
const logout = useCallback(() => {
setUser(null);
localStorage.removeItem('token');
}, []);
const value = useMemo(
() => ({ user, login, logout, loading }),
[user, login, logout, loading]
);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// Protected Route
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
if (loading) return <Loading />;
if (!user) return <Navigate to="/login" />;
return children;
}4. 表单状态管理
jsx
const FormContext = createContext();
function FormProvider({ initialValues, onSubmit, children }) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// 清除该字段的错误
setErrors(prev => ({ ...prev, [name]: undefined }));
}, []);
const handleBlur = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
}, []);
const handleSubmit = useCallback((e) => {
e.preventDefault();
// 验证
const newErrors = validate(values);
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
onSubmit(values);
}
}, [values, onSubmit]);
const value = useMemo(
() => ({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit
}),
[values, errors, touched, handleChange, handleBlur, handleSubmit]
);
return (
<FormContext.Provider value={value}>
<form onSubmit={handleSubmit}>
{children}
</form>
</FormContext.Provider>
);
}
function useFormContext() {
const context = useContext(FormContext);
if (!context) {
throw new Error('useFormContext must be used within FormProvider');
}
return context;
}
// Field 组件
function Field({ name, label, type = 'text' }) {
const { values, errors, touched, handleChange, handleBlur } =
useFormContext();
return (
<div>
<label>{label}</label>
<input
type={type}
value={values[name] || ''}
onChange={(e) => handleChange(name, e.target.value)}
onBlur={() => handleBlur(name)}
/>
{touched[name] && errors[name] && (
<span className="error">{errors[name]}</span>
)}
</div>
);
}
// 使用
<FormProvider
initialValues={{ email: '', password: '' }}
onSubmit={handleLogin}
>
<Field name="email" label="邮箱" type="email" />
<Field name="password" label="密码" type="password" />
<button type="submit">登录</button>
</FormProvider>Context vs 状态管理库
何时使用 Context
✅ 适合使用 Context 的场景:
- 主题、语言等全局配置
- 用户认证信息
- 不频繁变化的数据
- 中小型应用
❌ 不适合使用 Context 的场景:
- 频繁变化的数据
- 需要复杂的状态逻辑
- 需要中间件、DevTools
- 大型应用的全局状态
Context vs Redux
jsx
// Context 实现
const AppContext = createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
// Redux 实现
import { Provider } from 'react-redux';
import { createStore } from 'redux';
const store = createStore(reducer);
function App() {
return (
<Provider store={store}>
<Component />
</Provider>
);
}Context vs Zustand
jsx
// Context
const StoreContext = createContext();
function StoreProvider({ children }) {
const [bears, setBears] = useState(0);
const value = useMemo(
() => ({ bears, setBears }),
[bears]
);
return (
<StoreContext.Provider value={value}>
{children}
</StoreContext.Provider>
);
}
// Zustand(更简单)
import create from 'zustand';
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}));
// 使用更简单,且性能更好
function BearCounter() {
const bears = useStore((state) => state.bears);
return <div>{bears}</div>;
}最佳实践
1. 提供默认值和类型检查
typescript
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}2. 避免在 Context 中存储所有状态
jsx
// ❌ 不好:所有状态都放在 Context
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [ui, setUI] = useState({});
// ...更多状态
}
// ✅ 好:只在 Context 中存储真正需要共享的状态
const UserContext = createContext();
const ThemeContext = createContext();
// 局部状态留在组件内部
function PostList() {
const [posts, setPosts] = useState([]);
// posts 只在这里使用,不需要 Context
}3. 使用自定义 Hook 封装 Context
jsx
// ✅ 好的模式
const ThemeContext = createContext(undefined);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(
() => ({ theme, setTheme }),
[theme]
);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// 使用时只需导入 Hook
import { useTheme } from './ThemeContext';
function Component() {
const { theme } = useTheme();
return <div className={theme}>Content</div>;
}总结
Context 的核心要点:
基本概念
- 解决 prop drilling 问题
- 通过 Provider 提供值
- 通过 useContext 消费值
工作原理
- Provider 推入值栈
- Consumer 读取栈顶值
- 值变化时传播更新
性能优化
- 分割 Context
- 缓存 value
- 使用 memo
- 组件组合
最佳实践
- 自定义 Hook 封装
- 类型检查
- 合理的粒度
- 只存储真正需要共享的状态
Context 适合:
- 全局配置(主题、语言)
- 用户信息
- 不频繁变化的数据
不适合:
- 频繁变化的状态
- 复杂的状态逻辑
- 需要高性能优化的场景