痛点: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