Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

hook:useContext

  • 在函数子组件内引入上下文对象,并取出其中的变量或函数,供子组件使用
  • 核心作用: 避免逐层传递 props(props drilling),实现跨组件数据共享

使用步骤

  1. 创建 ContextcreateContext()
  2. 提供 Provider → 在顶层组件包裹 <Context.Provider>
  3. 消费数据 → 在子组件中使用 useContext(Context)

场景代码示例

  • 【场景 1】全局状态管理(最常用):

    • 在任意深度的子组件中访问全局状态

      第一步:创建 Context 和 Provider (GlobalProvider.tsx)

      import { createContext, useState } from "react";
      
      // 定义上下文类型
      interface GlobalContextType {
        currentId: string;
        setCurrentId: (id: string) => void;
      }
      
      // 创建 Context(提供默认值)
      export const GlobalContext = createContext<GlobalContextType>({
        currentId: "",
        setCurrentId: () => {},
      });
      
      // 创建 Provider 组件
      export const GlobalProvider = ({
        children,
      }: {
        children: React.ReactNode;
      }) => {
        const [currentId, setCurrentId] = useState("");
      
        return (
          <GlobalContext.Provider
            value={{
              currentId,
              setCurrentId,
            }}
          >
            {children}
          </GlobalContext.Provider>
        );
      };
      

      第二步:在顶层注入 Provider (index.tsx)

      import { createRoot } from "react-dom/client";
      import { GlobalProvider } from "./GlobalProvider";
      import App from "./App";
      
      const container = document.getElementById("root");
      const root = createRoot(container!);
      
      root.render(
        <GlobalProvider>
          <App />
        </GlobalProvider>
      );
      

      第三步:在任意子组件中使用 (ChildComponent.tsx)

      import { useContext } from "react";
      import { GlobalContext } from "../../GlobalProvider";
      
      const ChildComponent = () => {
        const { currentId, setCurrentId } = useContext(GlobalContext);
      
        return (
          <div>
            <p>当前 ID: {currentId}</p>
            <button onClick={() => setCurrentId("new-id")}>更新 ID</button>
          </div>
        );
      };
      
    • 如果要用以前的类去写,会很麻烦,需要使用 Consumer

      import { GlobalContext } from "../../GlobalProvider";
      
      class ChildComponent extends React.Component {
        render() {
          return (
            <GlobalContext.Consumer>
              {(value) => <div>当前 ID: {value.currentId}</div>}
            </GlobalContext.Consumer>
          );
        }
      }
      
  • 【场景 2】主题切换(常用):

    • 全局主题管理,切换明暗模式

      创建主题 Context (ThemeProvider.tsx)

      import { createContext, useState } from "react";
      
      type Theme = "light" | "dark";
      
      interface ThemeContextType {
        theme: Theme;
        toggleTheme: () => void;
      }
      
      export const ThemeContext = createContext<ThemeContextType>({
        theme: "light",
        toggleTheme: () => {},
      });
      
      export const ThemeProvider = ({
        children,
      }: {
        children: React.ReactNode;
      }) => {
        const [theme, setTheme] = useState<Theme>("light");
      
        const toggleTheme = () => {
          setTheme((prev) => (prev === "light" ? "dark" : "light"));
        };
      
        return (
          <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
          </ThemeContext.Provider>
        );
      };
      

      在组件中使用

      import { useContext } from "react";
      import { ThemeContext } from "./ThemeProvider";
      
      const ThemedButton = () => {
        const { theme, toggleTheme } = useContext(ThemeContext);
      
        return (
          <button
            style={{
              background: theme === "light" ? "#fff" : "#333",
              color: theme === "light" ? "#000" : "#fff",
            }}
            onClick={toggleTheme}
          >
            当前主题: {theme}
          </button>
        );
      };
      
  • 【场景 3】用户认证(常用):

    • 全局用户信息和登录状态管理

      创建认证 Context (AuthProvider.tsx)

      import { createContext, useState } from "react";
      
      interface User {
        id: string;
        name: string;
        email: string;
      }
      
      interface AuthContextType {
        user: User | null;
        login: (user: User) => void;
        logout: () => void;
        isAuthenticated: boolean;
      }
      
      export const AuthContext = createContext<AuthContextType>({
        user: null,
        login: () => {},
        logout: () => {},
        isAuthenticated: false,
      });
      
      export const AuthProvider = ({
        children,
      }: {
        children: React.ReactNode;
      }) => {
        const [user, setUser] = useState<User | null>(null);
      
        const login = (userData: User) => {
          setUser(userData);
        };
      
        const logout = () => {
          setUser(null);
        };
      
        return (
          <AuthContext.Provider
            value={{
              user,
              login,
              logout,
              isAuthenticated: !!user,
            }}
          >
            {children}
          </AuthContext.Provider>
        );
      };
      

      在组件中使用

      import { useContext } from "react";
      import { AuthContext } from "./AuthProvider";
      
      const UserProfile = () => {
        const { user, logout, isAuthenticated } = useContext(AuthContext);
      
        if (!isAuthenticated) {
          return <div>请先登录</div>;
        }
      
        return (
          <div>
            <p>欢迎, {user.name}</p>
            <button onClick={logout}>退出登录</button>
          </div>
        );
      };
      
  • 【场景 4】避免 Props Drilling(核心场景):

    • 无需逐层传递 props,直接在深层组件中获取数据

      问题:Props Drilling(❌ 不推荐)

      // 需要逐层传递 props,非常繁琐
      function App() {
        const [user, setUser] = useState({ name: "Alice" });
        return <Parent user={user} setUser={setUser} />;
      }
      
      function Parent({ user, setUser }) {
        return <Child user={user} setUser={setUser} />;
      }
      
      function Child({ user, setUser }) {
        return <GrandChild user={user} setUser={setUser} />;
      }
      
      function GrandChild({ user, setUser }) {
        return <div>{user.name}</div>; // 最终使用
      }
      

      解决方案:使用 Context(✅ 推荐)

      // 创建 Context
      const UserContext = createContext(null);
      
      function App() {
        const [user, setUser] = useState({ name: "Alice" });
        return (
          <UserContext.Provider value={{ user, setUser }}>
            <Parent />
          </UserContext.Provider>
        );
      }
      
      function Parent() {
        return <Child />; // 无需传递 props
      }
      
      function Child() {
        return <GrandChild />; // 无需传递 props
      }
      
      function GrandChild() {
        const { user } = useContext(UserContext); // 直接获取
        return <div>{user.name}</div>;
      }
      
  • 【场景 5】多个 Context 组合使用(常用):

    • 组合多个 Provider,分离关注点

      组合多个 Provider

      // index.tsx
      root.render(
        <AuthProvider>
          <ThemeProvider>
            <GlobalProvider>
              <App />
            </GlobalProvider>
          </ThemeProvider>
        </AuthProvider>
      );
      

      在组件中同时使用多个 Context

      import { useContext } from "react";
      import { AuthContext } from "./AuthProvider";
      import { ThemeContext } from "./ThemeProvider";
      
      const Dashboard = () => {
        const { user } = useContext(AuthContext);
        const { theme, toggleTheme } = useContext(ThemeContext);
      
        return (
          <div style={{ background: theme === "light" ? "#fff" : "#333" }}>
            <h1>欢迎, {user?.name}</h1>
            <button onClick={toggleTheme}>切换主题</button>
          </div>
        );
      };
      
  • 【场景 6】自定义 Hook 封装(最佳实践):

    • 创建自定义 Hook 简化使用

      封装 useAuth Hook

      // hooks/useAuth.ts
      import { useContext } from "react";
      import { AuthContext } from "../AuthProvider";
      
      export const useAuth = () => {
        const context = useContext(AuthContext);
        if (!context) {
          throw new Error("useAuth 必须在 AuthProvider 内部使用");
        }
        return context;
      };
      

      在组件中使用

      import { useAuth } from "./hooks/useAuth";
      
      const Profile = () => {
        const { user, logout } = useAuth(); // 更简洁,且有错误检查
      
        return (
          <div>
            <p>{user?.name}</p>
            <button onClick={logout}>退出</button>
          </div>
        );
      };
      

Context 使用流程对比

步骤类组件函数组件
创建 ContextcreateContext()createContext()
提供数据<Context.Provider value={...}><Context.Provider value={...}>
消费数据<Context.Consumer>{value => ...}</Context.Consumer>useContext(Context)
代码复杂度❌ 需要嵌套 Consumer,层级深✅ 一行代码,简洁明了

useContext vs Props

特性PropsContext
数据传递方式逐层传递跨层级直接访问
适用场景父子组件、兄弟组件全局状态、跨多层组件
代码可维护性层级多时维护困难更清晰
性能更好(精确控制)所有消费者都会重新渲染
使用复杂度简单直接需要创建 Context 和 Provider

选择原则:

  • 简单的父子组件 → 使用 Props
  • 跨多层组件、全局状态 → 使用 Context

常见应用场景

场景说明示例
全局状态跨组件共享的状态当前用户 ID、购物车数据
主题管理全局主题配置明暗模式切换
用户认证登录状态和用户信息登录、退出、权限验证
国际化 (i18n)多语言切换当前语言、翻译函数
路由信息当前路由状态React Router 的路由信息
表单状态复杂表单的状态管理多步骤表单、表单验证

注意事项

  • ⚠️ Context 变化会导致所有消费者重新渲染

    // ❌ 每次父组件渲染,value 都是新对象,导致所有消费者重新渲染
    const Parent = () => {
      const [count, setCount] = useState(0);
      return (
        <MyContext.Provider value={{ count, setCount }}>
          <Child />
        </MyContext.Provider>
      );
    };
    
    // ✅ 使用 useMemo 优化
    const Parent = () => {
      const [count, setCount] = useState(0);
      const value = useMemo(() => ({ count, setCount }), [count]);
      return (
        <MyContext.Provider value={value}>
          <Child />
        </MyContext.Provider>
      );
    };
    
  • ⚠️ 不要过度使用 Context

    // ❌ 简单的父子传递,不需要用 Context
    <Parent>
      <Child name="Alice" />
    </Parent>
    
    // ✅ 直接用 props 更简单
    
  • ⚠️ 在 Provider 外使用会报错

    // 使用自定义 Hook 添加错误检查
    export const useMyContext = () => {
      const context = useContext(MyContext);
      if (!context) {
        throw new Error("useMyContext 必须在 MyProvider 内部使用");
      }
      return context;
    };
    
  • 将 Context 拆分,避免不必要的重新渲染

    // ❌ 一个大 Context,任何值变化都会导致所有消费者重新渲染
    <GlobalContext.Provider value={{ user, theme, language, cart }}>
    
    // ✅ 拆分为多个小 Context
    <UserContext.Provider value={user}>
      <ThemeContext.Provider value={theme}>
        <LanguageContext.Provider value={language}>
          <CartContext.Provider value={cart}>
    
  • 使用自定义 Hook 封装 Context

    // 更好的 API,且包含错误检查
    const useAuth = () => useContext(AuthContext);
    const useTheme = () => useContext(ThemeContext);