痛点:prop drilling
<App theme="dark">
<Page theme="dark">
<Sidebar theme="dark">
<Item theme="dark"> {/* 一路传 4 层 */}
中间几层用不到 theme,只是搬运工。
Context 解决它
// 1. 创建
const ThemeContext = createContext<'light' | 'dark'>('light');
// 2. 提供
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
// 3. 消费(在任意后代)
function Item() {
const theme = useContext(ThemeContext);
return <div className={theme}>...</div>;
}
React 19+ 可以直接写
<ThemeContext value="dark">,省了.Provider。
默认值的两个用途
const Ctx = createContext('light');
- 没被 Provider 包裹的组件读到
'light' - 给类型推断用(TS 自动推
string)
实际项目里大多数 Context 没默认值,需要 Provider 才能用——这时给个 null,消费者用自定义 hook 判空:
const Ctx = createContext<User | null>(null);
function useUser() {
const u = useContext(Ctx);
if (!u) throw new Error('useUser 必须在 UserProvider 内');
return u;
}
Provider 的 value 引用稳定
// ❌ 每次 Parent re-render 都是新对象 → 所有消费者全 re-render
<Ctx.Provider value={{ user, logout }}>
// ✅ memo 化
const value = useMemo(() => ({ user, logout }), [user, logout]);
<Ctx.Provider value={value}>
Context 不适合什么
不适合频繁变化的状态——任何消费者都会 re-render,不能"只订阅一部分"。
❌ 把整个应用 state 塞一个 Context → 改任何字段 全树重新渲染
✅ 拆多个 Context:
<AuthContext.Provider>
<ThemeContext.Provider>
<CartContext.Provider>
<App />
或者用真正的状态管理库(Zustand / Redux),它们支持"选择器订阅"。
实战:主题切换
type Theme = 'light' | 'dark';
const ThemeCtx = createContext<{
theme: Theme;
toggle: () => void;
} | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('light');
const value = useMemo(() => ({
theme,
toggle: () => setTheme(t => t === 'light' ? 'dark' : 'light'),
}), [theme]);
return <ThemeCtx.Provider value={value}>{children}</ThemeCtx.Provider>;
}
export function useTheme() {
const ctx = useContext(ThemeCtx);
if (!ctx) throw new Error('useTheme 需要在 ThemeProvider 内');
return ctx;
}
用法:
function ToggleBtn() {
const { theme, toggle } = useTheme();
return <button onClick={toggle}>当前 {theme}</button>;
}
Context 嵌套
<A.Provider value={...}>
<B.Provider value={...}>
<C.Provider value={...}>
<App />
可以嵌套很深。如果 Provider 太多,写个 wrapper:
function Providers({ children }: { children: React.ReactNode }) {
return (
<A.Provider value={...}>
<B.Provider value={...}>
<C.Provider value={...}>{children}</C.Provider>
</B.Provider>
</A.Provider>
);
}
选型
| 场景 | 用 |
|---|---|
| 主题、语言、当前用户 | Context |
| 表单全局错误 / 设置 | Context |
| Server 状态(API 数据) | TanStack Query / SWR |
| 复杂客户端状态(购物车、wizard) | Zustand / Jotai |
| 巨型应用、需要时间旅行调试 | Redux Toolkit |
→ 下一篇 useReducer