hook:useContext
- 在函数子组件内引入上下文对象,并取出其中的变量或函数,供子组件使用
- 核心作用: 避免逐层传递 props(props drilling),实现跨组件数据共享
使用步骤
- 创建 Context →
createContext() - 提供 Provider → 在顶层组件包裹
<Context.Provider> - 消费数据 → 在子组件中使用
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> ); }; -
如果要用以前的类去写,会很麻烦,需要使用
Consumerimport { 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 使用流程对比
| 步骤 | 类组件 | 函数组件 |
|---|---|---|
| 创建 Context | createContext() | createContext() |
| 提供数据 | <Context.Provider value={...}> | <Context.Provider value={...}> |
| 消费数据 | <Context.Consumer>{value => ...}</Context.Consumer> | useContext(Context) |
| 代码复杂度 | ❌ 需要嵌套 Consumer,层级深 | ✅ 一行代码,简洁明了 |
useContext vs Props
| 特性 | Props | Context |
|---|---|---|
| 数据传递方式 | 逐层传递 | 跨层级直接访问 |
| 适用场景 | 父子组件、兄弟组件 | 全局状态、跨多层组件 |
| 代码可维护性 | 层级多时维护困难 | 更清晰 |
| 性能 | 更好(精确控制) | 所有消费者都会重新渲染 |
| 使用复杂度 | 简单直接 | 需要创建 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);