当前位置: 首页 > news >正文

02-Hooks完全指南——03-useContext 与跨组件通信

3-

useContext 与跨组件通信

一、Context 基础

1.1 什么是 Context?

Context 提供了一种在组件树中传递数据的方法,无需手动逐层传递 props。它解决了"props drilling"(属性钻取)的问题。

1.2 何时使用 Context

场景是否使用 Context说明
主题(深色/浅色模式)✅ 推荐全局样式偏好
用户认证信息✅ 推荐登录状态、用户资料
语言/本地化✅ 推荐多语言切换
表单状态⚠️ 谨慎简单表单用 props
UI 状态(模态框开关)❌ 不推荐用状态提升
路由状态✅ 推荐路由库内部使用

二、Context API 基础用法

2.1 创建和使用 Context

// 1. 创建 Context const ThemeContext = React.createContext('light'); // 2. 提供者组件 function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); } // 3. 消费 Context(函数组件) function ThemedButton() { const { theme, setTheme } = useContext(ThemeContext); return ( <button style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#333' : '#fff' }} onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} > 切换主题 </button> ); } // 4. 使用 function App() { return ( <ThemeProvider> <ThemedButton /> </ThemeProvider> ); }

2.2 默认值

// 默认值仅在 Provider 缺失时使用 const UserContext = React.createContext({ name: '游客', role: 'guest' }); function WelcomeMessage() { const user = useContext(UserContext); return <div>欢迎, {user.name}!</div>; } // 没有 Provider,使用默认值 <WelcomeMessage /> // 显示 "欢迎, 游客!"

三、Context 进阶用法

3.1 多个 Context

const ThemeContext = React.createContext(); const UserContext = React.createContext(); const LanguageContext = React.createContext(); function App() { const [theme, setTheme] = useState('light'); const [user, setUser] = useState(null); const [language, setLanguage] = useState('zh-CN'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <UserContext.Provider value={{ user, setUser }}> <LanguageContext.Provider value={{ language, setLanguage }}> <Dashboard /> </LanguageContext.Provider> </UserContext.Provider> </ThemeContext.Provider> ); } function Dashboard() { const { theme } = useContext(ThemeContext); const { user } = useContext(UserContext); const { language } = useContext(LanguageContext); return ( <div className={`theme-${theme}`}> <h1>{user?.name || '未登录'}</h1> <p>当前语言: {language}</p> </div> ); }

3.2 自定义 Provider 组件

// 创建 Context const AuthContext = React.createContext(); // 自定义 Provider export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const login = async (email, password) => { try { setLoading(true); const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) }); const data = await response.json(); setUser(data.user); localStorage.setItem('token', data.token); } catch (err) { setError(err.message); } finally { setLoading(false); } }; const logout = () => { setUser(null); localStorage.removeItem('token'); }; const value = { user, loading, error, login, logout, isAuthenticated: !!user }; return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> ); } // 自定义 Hook 方便使用 export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; } // 使用 function LoginButton() { const { login, isAuthenticated, logout } = useAuth(); if (isAuthenticated) { return <button onClick={logout}>退出</button>; } return <button onClick={() => login('user@example.com', 'password')}>登录</button>; }

3.3 Context + useReducer

// 定义状态和 reducer const initialState = { user: null, theme: 'light', notifications: [], sidebarOpen: true }; function appReducer(state, action) { switch (action.type) { case 'SET_USER': return { ...state, user: action.payload }; case 'TOGGLE_THEME': return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' }; case 'ADD_NOTIFICATION': return { ...state, notifications: [...state.notifications, action.payload] }; case 'TOGGLE_SIDEBAR': return { ...state, sidebarOpen: !state.sidebarOpen }; default: return state; } } // 创建 Context const AppContext = React.createContext(); // Provider export function AppProvider({ children }) { const [state, dispatch] = useReducer(appReducer, initialState); const value = { state, dispatch }; return <AppContext.Provider value={value}>{children}</AppContext.Provider>; } // 自定义 Hook export function useAppContext() { const context = useContext(AppContext); if (!context) { throw new Error('useAppContext must be used within AppProvider'); } return context; } // 使用 function ThemeToggle() { const { state, dispatch } = useAppContext(); return ( <button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}> 当前主题: {state.theme} </button> ); }

四、性能优化

4.1 使用 useMemo 避免不必要的重渲染

function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); // ✅ 使用 useMemo 缓存 value const value = useMemo(() => ({ theme, setTheme }), [theme]); return ( <ThemeContext.Provider value={value}> {children} </ThemeContext.Provider> ); }

4.2 拆分 Context

// ❌ 不好的做法:所有状态放在一个 Context 中 const AppContext = React.createContext(); // 任何状态变化都会导致所有消费者重渲染 // ✅ 好的做法:按职责拆分 Context const UserContext = React.createContext(); const ThemeContext = React.createContext(); const NotificationContext = React.createContext(); function App() { const [user, setUser] = useState(null); const [theme, setTheme] = useState('light'); const [notifications, setNotifications] = useState([]); return ( <UserContext.Provider value={{ user, setUser }}> <ThemeContext.Provider value={{ theme, setTheme }}> <NotificationContext.Provider value={{ notifications, setNotifications }}> <MainContent /> </NotificationContext.Provider> </ThemeContext.Provider> </UserContext.Provider> ); }

4.3 使用 React.memo 优化消费者

const ExpensiveComponent = React.memo(({ data }) => { // 复杂渲染逻辑 return <div>{/* ... */}</div>; }); function Parent() { const { user } = useContext(UserContext); // ExpensiveComponent 只在 user 变化时重渲染 return <ExpensiveComponent data={user} />; }

五、实战案例

5.1 主题切换系统

const ThemeContext = React.createContext(); const themes = { light: { background: '#ffffff', color: '#333333', primary: '#007bff' }, dark: { background: '#1a1a1a', color: '#ffffff', primary: '#4dabf7' } }; export function ThemeProvider({ children }) { const [themeName, setThemeName] = useState('light'); const theme = themes[themeName]; const toggleTheme = () => { setThemeName(prev => prev === 'light' ? 'dark' : 'light'); }; const value = useMemo(() => ({ theme, themeName, toggleTheme }), [theme, themeName]); return ( <ThemeContext.Provider value={value}> {children} </ThemeContext.Provider> ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; } // 使用 function ThemedComponent() { const { theme, toggleTheme } = useTheme(); return ( <div style={{ background: theme.background, color: theme.color }}> <button onClick={toggleTheme}>切换主题</button> <p>当前主题</p> </div> ); }

5.2 多语言系统

const LanguageContext = React.createContext(); const translations = { 'zh-CN': { welcome: '欢迎', logout: '退出', settings: '设置' }, 'en-US': { welcome: 'Welcome', logout: 'Logout', settings: 'Settings' } }; export function LanguageProvider({ children }) { const [locale, setLocale] = useState('zh-CN'); const t = translations[locale]; const value = useMemo(() => ({ locale, setLocale, t, tKey: (key) => t[key] || key }), [locale, t]); return ( <LanguageContext.Provider value={value}> {children} </LanguageContext.Provider> ); } export function useLanguage() { const context = useContext(LanguageContext); if (!context) { throw new Error('useLanguage must be used within LanguageProvider'); } return context; } // 使用 function Header() { const { t, locale, setLocale } = useLanguage(); return ( <header> <h1>{t.welcome}</h1> <select value={locale} onChange={(e) => setLocale(e.target.value)}> <option value="zh-CN">中文</option> <option value="en-US">English</option> </select> </header> ); }

5.3 购物车状态管理

const CartContext = React.createContext(); function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': { const existing = state.items.find(item => item.id === action.payload.id); if (existing) { return { ...state, items: state.items.map(item => item.id === action.payload.id ? { ...item, quantity: item.quantity + 1 } : item ) }; } return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] }; } case 'REMOVE_ITEM': return { ...state, items: state.items.filter(item => item.id !== action.payload) }; case 'UPDATE_QUANTITY': return { ...state, items: state.items.map(item => item.id === action.payload.id ? { ...item, quantity: Math.max(0, action.payload.quantity) } : item ).filter(item => item.quantity > 0) }; case 'CLEAR_CART': return { ...state, items: [] }; default: return state; } } export function CartProvider({ children }) { const [state, dispatch] = useReducer(cartReducer, { items: [] }); const addItem = (item) => dispatch({ type: 'ADD_ITEM', payload: item }); const removeItem = (id) => dispatch({ type: 'REMOVE_ITEM', payload: id }); const updateQuantity = (id, quantity) => dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } }); const clearCart = () => dispatch({ type: 'CLEAR_CART' }); const totalItems = state.items.reduce((sum, item) => sum + item.quantity, 0); const totalPrice = state.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); const value = { cart: state.items, totalItems, totalPrice, addItem, removeItem, updateQuantity, clearCart }; return ( <CartContext.Provider value={value}> {children} </CartContext.Provider> ); } export function useCart() { const context = useContext(CartContext); if (!context) { throw new Error('useCart must be used within CartProvider'); } return context; }

六、常见陷阱

6.1 Provider 缺失导致错误

// ❌ 忘记包裹 Provider function App() { return <ComponentThatUsesContext />; // 会使用默认值或报错 } // ✅ 确保 Provider 存在 function App() { return ( <ThemeProvider> <ComponentThatUsesContext /> </ThemeProvider> ); }

6.2 不必要的重渲染

// ❌ 每次渲染都创建新对象 function BadProvider({ children }) { const [count, setCount] = useState(0); return ( <MyContext.Provider value={{ count, setCount }}> {children} </MyContext.Provider> ); } // ✅ 使用 useMemo function GoodProvider({ children }) { const [count, setCount] = useState(0); const value = useMemo(() => ({ count, setCount }), [count]); return ( <MyContext.Provider value={value}> {children} </MyContext.Provider> ); }

七、练习题

基础题

  1. 实现一个主题切换功能,包含亮色/暗色两种主题
  2. 实现一个用户登录状态管理,显示登录用户信息

进阶题

  1. 实现一个多语言切换系统
  2. 实现一个全局通知系统,任何组件都可以发送通知

参考答案

// 通知系统 const NotificationContext = React.createContext(); function NotificationProvider({ children }) { const [notifications, setNotifications] = useState([]); const addNotification = (message, type = 'info') => { const id = Date.now(); setNotifications(prev => [...prev, { id, message, type }]); setTimeout(() => { setNotifications(prev => prev.filter(n => n.id !== id)); }, 3000); }; const removeNotification = (id) => { setNotifications(prev => prev.filter(n => n.id !== id)); }; return ( <NotificationContext.Provider value={{ notifications, addNotification, removeNotification }}> {children} <NotificationList /> </NotificationContext.Provider> ); } function NotificationList() { const { notifications, removeNotification } = useContext(NotificationContext); return ( <div className="notifications"> {notifications.map(notif => ( <div key={notif.id} className={`notification ${notif.type}`}> {notif.message} <button onClick={() => removeNotification(notif.id)}>×</button> </div> ))} </div> ); }

八、小结

要点说明
适用场景主题、用户认证、语言等全局状态
性能优化拆分 Context、使用 useMemo
自定义 Hook封装 useContext 使用
默认值仅在无 Provider 时生效

核心要点:

  • Context 解决 props drilling 问题
  • 不要过度使用 Context
  • 配合 useReducer 管理复杂状态
  • 注意性能影响,适当拆分

http://www.zskr.cn/news/1490844.html

相关文章:

  • HarmonyOS 手写笔服务:让你的应用支持手写输入
  • AMD Ryzen调试终极指南:5分钟掌握SMU Debug Tool完整教程
  • 济南千鸿黄金回收市中区门店 - 润富黄金回收
  • 从多普勒效应到代码:深入理解无线通信中的频率偏移与同步(以QPSK/16QAM为例)
  • 大模型评估体系全解:如何科学衡量你的 LLM 应用质量?
  • 如何用Dify工作流模板快速构建专业级AI应用?实战方法揭秘
  • 全程用 AI 做一款商业级手游 · EP9 收尾与复盘:做到了哪,没做到哪,边界在哪
  • 2026年加固笔记本电脑应用白皮书智能制造领域解析:防爆计算机/三防电脑/便携式加固计算机/实力盘点 - 优质品牌商家
  • Java TCP双人在线五子棋实战项目:含可运行客户端/服务端源码与课程设计报告
  • 济南余生黄金回收历下区旗舰店 - 润富黄金回收
  • 生产级机器学习系统:从模型部署到合规治理的全链路实践
  • 别再让网卡拖慢你的服务器!手把手教你调优RPS/RFS,实测CPU负载下降30%
  • 3步实现QQ音乐加密格式转换:qmc-decoder完整实战指南
  • GPT-5.5 技术深度解析与企业级生产落地实战:从幻觉率下降到百万Token工程化
  • 预训练任务演进史:从掩码建模到世界模型的认知跃迁
  • 用Cheat Engine 7.5给《植物大战僵尸》改个“无限阳光”:从找地址到写指针的保姆级教程
  • 2026数据分析对报考大数据专业的价值分析
  • 佛山余生黄金回收全国连锁24小时上门实测 - 润富黄金回收
  • Mac Mouse Fix:解锁第三方鼠标在macOS上的全部潜能
  • 2026年评价高的苏州POM塑料粒子/苏州ABS塑料粒子/LCP塑料粒子/PPO塑料粒子生产厂家推荐 - 行业平台推荐
  • 别再手动调Excel了!用Python的openpyxl批量设置样式(字体/边框/填充)保姆级教程
  • 数据辅导不是教技术,而是做认知手术
  • 2026年地面洗地机品牌排行榜:史沃斯、挑战者、厉邦谁更强? - 工业清洁测评社
  • STM32的FMC不只是内存控制器:驱动TFT屏、AD7606等外设的‘万能总线’实战
  • FusionCompute 8.0 实验环境搭建:手把手教你用VRM镜像直装代替安装工具
  • AI总入口
  • THULAC高级功能探索:繁体转简体与过滤器的实用技巧
  • Rack::Cache源码解读:核心类与关键方法的深度分析
  • 高通QFIL工具保姆级教程:从9008模式进到完整分区读写(附常见失败原因)
  • 2026年比较好的东台船用不锈钢精密铸造件/五金不锈钢精密铸造件/仪表不锈钢精密铸造件多家厂家对比分析 - 品牌宣传支持者