人人都会AI编程

useContext:上下文消费

更新时间:2026-07-10

useContext 是 React 提供的基础 Hook,用于在函数组件中读取上下文(Context)的值。它解决了跨层级组件通信时 Props 层层传递(Props Drilling)的问题,让深层子组件可以直接访问祖先组件提供的数据。

基本用法

使用 Context 分为三步:

1. 创建上下文

import { createContext } from 'react';

// 创建一个主题上下文,默认值为 'light'
const ThemeContext = createContext('light');

2. 在上层组件提供值

function App() {
  const [theme, setTheme] = useState('dark');

  return (
    // Provider 包裹需要共享数据的子组件树
    <ThemeContext.Provider value={theme}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

3. 在任意子组件中消费

import { useContext } from 'react';

function ThemedButton() {
  // 直接获取当前的上下文值,无需通过中间组件传递
  const theme = useContext(ThemeContext);

  return (
    <button className={`btn btn-${theme}`}>
      当前主题:{theme}
    </button>
  );
}

useContext 接收一个上下文对象(由 createContext 创建),返回该上下文当前的 value。当 Provider 的 value 变化时,所有使用该上下文的组件都会自动重新渲染。

典型应用场景

1. 全局主题切换

const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 在任意深层组件中直接使用
function Header() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <header className={theme}>
      <button onClick={toggleTheme}>切换主题</button>
    </header>
  );
}

2. 用户认证信息共享

const AuthContext = createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const login = () => setUser({ name: 'Alice' });
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

function UserMenu() {
  const { user, login, logout } = useContext(AuthContext);
  return user ? (
    <div>欢迎, {user.name} <button onClick={logout}>退出</button></div>
  ) : (
    <button onClick={login}>登录</button>
  );
}

3. 国际化语言切换

const LocaleContext = createContext('zh-CN');

function Greeting() {
  const locale = useContext(LocaleContext);
  return <h1>{locale === 'zh-CN' ? '你好' : 'Hello'}</h1>;
}

与 Props Drilling 的对比

没有 Context 时,数据需要通过每一层组件显式传递,即使中间组件并不使用该数据:

// 痛苦的逐层传递
function App() {
  const [user, setUser] = useState(null);
  return <Layout user={user} onLogin={setUser} />;
}

function Layout({ user, onLogin }) {
  return <Header user={user} onLogin={onLogin} />;
}

function Header({ user, onLogin }) {
  return <UserMenu user={user} onLogin={onLogin} />;
}

使用 Context 后,中间组件完全不需要感知数据的存在,直接消除无意义的传递。

性能考量:避免不必要的重渲染

useContext 的一个常见误区是:每当 Provider 的 value 变化时,所有消费该上下文的组件都会重新渲染,即使该组件只使用了 value 的一部分。这意味着 Context 不适合存放频繁变化且粒度较细的状态。

优化策略:

  1. 拆分多个 Context

将不同关注点的状态分别放入不同的 Context,这样只有在相关状态变化时才会触发对应组件的重渲染。

  1. 将 value 包裹在 useMemo 中

如果 Provider 的 value 是一个对象或数组,每次渲染都会创建新的引用,导致所有消费者无谓重渲染。用 useMemo 缓存 value:

   function ThemeProvider({ children }) {
     const [theme, setTheme] = useState('light');
     const contextValue = useMemo(() => ({ theme, setTheme }), [theme]);
     return (
       <ThemeContext.Provider value={contextValue}>
         {children}
       </ThemeContext.Provider>
     );
   }
   
  1. 对于高频更新的状态(如输入框内容),Context 不是最佳选择

考虑使用状态管理库或 useReducer + Props 下传,避免全局性的渲染抖动。

总结

useContext 让跨层级数据共享变得简洁优雅,是解决 Props Drilling 的首选工具。但要注意其“全局广播”式的更新特性,合理设计 Context 的粒度和 value 的稳定性,才能兼顾开发便利性和渲染性能。