当前位置: 首页 > 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.jsqmd.com/news/979117/

相关文章:

  • LLM数据生命周期防护:面向大模型的动态DLP实践指南
  • HsMod:基于BepInEx的炉石传说深度定制框架
  • 数据社区即服务(DCaaS):数据从业者的职业加速器
  • 终极指南:用antimicrox让所有游戏都支持手柄控制的完整教程
  • 别再只配环境变量了!PyInstaller打包exe时Tcl报错的深层原因与一劳永逸的解法
  • Horos医疗影像软件完全指南:如何在Mac上免费实现专业级医学图像分析
  • HarmonyOS 手写笔服务:让你的应用支持手写输入
  • K210+240*240分辨率数据集制作:从自动拍照脚本到VOTT标注一条龙
  • 济南千鸿黄金回收市中区门店 - 润富黄金回收
  • AMD Ryzen调试终极指南:5分钟掌握SMU Debug Tool完整教程
  • BuildingBlocks适配器模式应用指南:掌握RecyclerView与ViewPager高级用法
  • 2026年商用鸳鸯火锅底料现场试料品牌实测排行:九宫格火锅底料/川味火锅底料/清汤火锅底料/清油火锅底料/番茄底料/选择指南 - 优质品牌商家
  • PARL框架:AI Agent的分布式事件驱动执行范式
  • 免费彩色表情字体EmojiOne Color:让你的设计瞬间“活“起来的终极指南
  • 从多普勒效应到代码:深入理解无线通信中的频率偏移与同步(以QPSK/16QAM为例)
  • 终极指南:使用JBZoo/Utils快速检测PHP环境和监控系统信息 [特殊字符]
  • 如何探索云音乐歌词提取的智能解决方案
  • 大模型评估体系全解:如何科学衡量你的 LLM 应用质量?
  • 2026年加固笔记本电脑应用白皮书智能制造领域解析:防爆计算机/三防电脑/便携式加固计算机/实力盘点 - 优质品牌商家
  • 跟我一起学“仓颉”设计模式-原型模式练习题
  • 你的STM32项目复位不可靠?可能是忽略了这3个电容的细节(附选型指南)
  • 告别‘php不是命令’:用PHPStudy一键配置环境变量的隐藏技巧与原理
  • 如何用Dify工作流模板快速构建专业级AI应用?实战方法揭秘
  • 全程用 AI 做一款商业级手游 · EP9 收尾与复盘:做到了哪,没做到哪,边界在哪
  • 2026河北混合型塑胶跑道专业服务商排行及能力解析:河北预制型塑胶跑道/硅pu学校篮球场/硅pu排球场/硅pu材料/选择指南 - 优质品牌商家
  • 排查SNMP Trap收不到?手把手教你用Wireshark和MIB Browser定位问题(附端口占用解决)
  • 珠海余生黄金回收:全国连锁黄金回收测评 - 润富黄金回收
  • 别再让亚稳态坑你!FPGA跨时钟域(CDC)单bit信号处理的3个实战避坑指南
  • 2026年喷雾干燥机技术解析与靠谱品牌实测对比:旋转闪蒸烘干机/桨叶干燥机/气流烘干机/流化床干燥机/滚筒刮板烘干机/选择指南 - 优质品牌商家
  • 济南余生黄金回收历下区旗舰店 - 润富黄金回收