从 Props 传递到 Context:useContext 如何优化组件结构

120 阅读3分钟

从 Props 传递到 Context:useContext 如何优化组件结构

在开发复杂的 React 应用时,我们经常会遇到这样的问题:如何在组件树中多个层级之间共享状态或配置信息? 虽然可以通过 props 一层层传递数据,但这种方式在嵌套较深的情况下会变得非常繁琐且难以维护。

为了解决这个问题,React 提供了 上下文(Context)API,而 useContext 是函数组件中访问上下文的核心 Hook。本文将详细介绍 useContext 的使用方式、适用场景以及最佳实践。

一、什么是 useContext

useContext 是 React 提供的一个 Hook,用于在函数组件中直接访问和订阅上下文(Context)的值。它允许直接访问上下文的值,而无需通过 props 层层传递。

基本语法:

const value = useContext(Context);
  • Context 是通过 React.createContext() 创建的上下文对象。
  • 返回的是当前上下文中 Provider 所提供的值。

二、使用 useContext 的步骤

  1. 创建上下文

    首先,新建一个js文件,创建一个上下文对象,并提供默认值(可选)。

    import { createContext } from "react";
    
    export const ThemeContext = createContext("light");
    
  2. 使用 Provider 提供上下文值

    然后,在组件树的某个父级位置使用 <ThemeContext.Provider> 来提供上下文值。

    import { useState } from "react";
    import Page from "./component/Page";
    import "./App.css";
    import { ThemeContext } from "./ThemeContext";
    import { useContext } from "react";
    function App() {
      const themeContext = useContext(ThemeContext);
      
      const [theme, setTheme] = useState(themeContext.theme);
      return (
        // 1. 用ThemeContext.Provider包裹住App组件的所有内容, 为内部的组件提供服务。
        <div className="container">
          <ThemeContext.Provider value={theme}>
            <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
              切换为{theme === "dark" ? "浅色" : "深色"}
            </button>
            <Page></Page>
          </ThemeContext.Provider>
        </div>
      );
    }
    
    export default App;
    
    
  3. 在子组件中使用 useContext 消费上下文

    最后,在任意深层子组件中,都可以通过 useContext 直接获取上下文值,无需显式传递 props。

    Page组件

    import Child from "@/component/Child";
    import { ThemeContext } from "@/ThemeContext";
    import { useContext } from "react";
    export default function Page() {
      const theme = useContext(ThemeContext);
      return (
        <>
          {theme}
          <Child></Child>
        </>
      );
    }
    
    

    Child组件

    import { useContext } from "react";
    import { ThemeContext } from "@/ThemeContext";
    export default function Child() {
      const theme = useContext(ThemeContext);
      return (
        <>
          <div className={theme}>
            我是子组件
          </div>
        </>
      );
    }
    
    

    实现效果

    useContext.gif

三、实际应用场景

  • 场景一:全局主题管理

    这是最经典的使用场景之一。你可以将应用的主题(如暗黑模式/亮色模式)通过上下文共享给所有需要该信息的组件。

    const ThemeContext = createContext('light');
    
    function App() {
      return (
        <ThemeContext.Provider value="dark">
          <Header />
          <MainContent />
          <Footer />
        </ThemeContext.Provider>
      );
    }
    
  • 场景二:用户认证信息共享

    登录用户的信息通常在整个应用中都需要访问,例如用户名、权限等。使用上下文可以避免将这些信息层层传递。

    const UserContext = createContext(null);
    
    function App({ user }) {
      return (
        <UserContext.Provider value={user}>
          <Profile />
        </UserContext.Provider>
      );
    }
    

四、注意事项

  1. 不要滥用 Context

    虽然上下文可以跨层级共享数据,但它并不是万能的。过度使用 Context 可能会导致:

    • 组件复用性降低;
    • 状态变更不可预测;
    • 难以追踪和调试。

    建议仅将 Context 用于真正需要共享的“全局”状态,比如主题、语言、用户信息等。

  2. 注意性能优化

    value 发生变化时,所有使用该上下文的组件都会重新渲染。为了避免不必要的渲染,你可以结合 useMemoReact.memo 进行优化。

    const theme = useMemo(() => ({ mode: 'dark', toggle: () => setMode('light') }), [mode]);
    
    <ThemeContext.Provider value={theme}>
      ...
    </ThemeContext.Provider>
    
  3. Context 更新依赖引用一致性

    如果上下文的值是一个对象,即使内容没有变化,只要引用不同,也会触发重新渲染。因此推荐使用 useMemo 包裹对象类型的上下文值。