React Context 实战指南
React Context 是 React 内置的状态管理方案,可以让组件树共享状态。本文将全面介绍 React Context 的核心概念、实战应用和最佳实践。
一、Context 基础
1.1 什么是 Context?
Context 提供了一种在组件树中共享数据的方式,而不需要通过 props 逐层传递。
1.2 创建 Context
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemeProvider({ children }) { const theme = 'dark';
return ( <ThemeContext.Provider value={theme}> {children} </ThemeContext.Provider> ); }
function ThemeDisplay() { const theme = useContext(ThemeContext);
return <div>当前主题: {theme}</div>; }
function App() { return ( <ThemeProvider> <ThemeDisplay /> </ThemeProvider> ); }
|
二、Context + Hook
2.1 自定义 Hook
import { createContext, useContext, useState, useEffect } from 'react';
const ThemeContext = createContext(null);
function ThemeProvider({ children }) { const [theme, setTheme] = useState('light');
const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); };
return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }
function useTheme() { const context = useContext(ThemeContext);
if (!context) { throw new Error('useTheme 必须在 ThemeProvider 中使用'); }
return context; }
function ThemeDisplay() { const { theme, toggleTheme } = useTheme();
return ( <div> <button onClick={toggleTheme}> 切换到 {theme === 'light' ? '深色' : '浅色'} 主题 </button> <p>当前主题: {theme}</p> </div> ); }
|
2.2 Provider 的嵌套
function App() { const user = { name: '张三', age: 25 };
return ( <UserContext.Provider value={user}> <ThemeContext.Provider value={{ theme: 'dark', toggle: () => {} }}> <Layout> <Navbar /> <Content /> </Layout> </ThemeContext.Provider> </UserContext.Provider> ); }
|
三、实战案例
3.1 主题切换
import { createContext, useContext, useState, useEffect } from 'react';
const ThemeContext = createContext(null);
function ThemeProvider({ children }) { const [theme, setTheme] = useState(() => { const saved = localStorage.getItem('theme'); return saved || 'light'; });
useEffect(() => { const root = document.documentElement; root.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); }, [theme]);
const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); };
return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }
function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme 必须在 ThemeProvider 中使用'); } return context; }
function App() { return ( <ThemeProvider> <ThemeSwitcher /> <Content /> </ThemeProvider> ); }
function ThemeSwitcher() { const { theme, toggleTheme } = useTheme();
return ( <button onClick={toggleTheme}> 切换主题: {theme} </button> ); }
function Content() { const { theme } = useTheme();
return ( <div style={{ padding: 20, backgroundColor: theme === 'dark' ? '#1a1a1a' : '#ffffff' }}> <h1>主题: {theme}</h1> <p>这是一个示例内容</p> </div> ); }
|
3.2 用户认证
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext(null);
function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { const savedUser = localStorage.getItem('user'); if (savedUser) { setUser(JSON.parse(savedUser)); } setLoading(false); }, []);
const login = async (email, password) => { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });
if (response.ok) { const userData = await response.json(); setUser(userData); localStorage.setItem('user', JSON.stringify(userData)); } else { throw new Error('登录失败'); } };
const logout = () => { setUser(null); localStorage.removeItem('user'); };
return ( <AuthContext.Provider value={{ user, login, logout, loading }}> {children} </AuthContext.Provider> ); }
function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth 必须在 AuthProvider 中使用'); } return context; }
function LoginForm() { const { login } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState('');
const handleSubmit = async (e) => { e.preventDefault(); try { await login(email, password); setError(''); } catch (error) { setError('登录失败,请检查用户名和密码'); } };
return ( <form onSubmit={handleSubmit}> {error && <p style={{ color: 'red' }}>{error}</p>} <div> <label>邮箱: </label> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> <div> <label>密码: </label> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> <button type="submit">登录</button> </form> ); }
function UserProfile() { const { user } = useAuth();
if (!user) { return <LoginForm />; }
return ( <div> <p>欢迎, {user.name}</p> <p>邮箱: {user.email}</p> <button onClick={logout}>退出登录</button> </div> ); }
function ProtectedRoute({ children }) { const { user } = useAuth();
if (!user) { return <LoginForm />; }
return children; }
function App() { return ( <AuthProvider> <Navbar /> <ProtectedRoute> <Content /> </ProtectedRoute> </AuthProvider> ); }
|
3.3 国际化(i18n)
import { createContext, useContext, useState, useEffect } from 'react';
const translations = { en: { welcome: 'Welcome', title: 'My App', login: 'Login', logout: 'Logout' }, zh: { welcome: '欢迎', title: '我的应用', login: '登录', logout: '退出' } };
const I18nContext = createContext(null);
function I18nProvider({ children }) { const [lang, setLang] = useState(() => { return localStorage.getItem('lang') || 'en'; });
const t = (key) => translations[lang][key] || key;
const changeLang = (newLang) => { setLang(newLang); localStorage.setItem('lang', newLang); };
return ( <I18nContext.Provider value={{ lang, t, changeLang }}> {children} </I18nContext.Provider> ); }
function useI18n() { const context = useContext(I18nContext); if (!context) { throw new Error('useI18n 必须在 I18nProvider 中使用'); } return context; }
function LanguageSwitcher() { const { lang, changeLang } = useI18n();
return ( <select value={lang} onChange={(e) => changeLang(e.target.value)}> <option value="en">English</option> <option value="zh">中文</option> </select> ); }
function Header() { const { t } = useI18n();
return ( <header> <h1>{t('title')}</h1> <LanguageSwitcher /> </header> ); }
function App() { return ( <I18nProvider> <Header /> <Content /> </I18nProvider> ); }
|
四、性能优化
4.1 减少重渲染
function App() { const [count, setCount] = useState(0); return ( <CountContext.Provider value={{ count, setCount }}> <CounterDisplay /> <CounterControls /> </CountContext.Provider> ); }
function App() { const [count, setCount] = useState(0); const contextValue = useMemo(() => ({ count, setCount }), [count, setCount]);
return ( <CountContext.Provider value={contextValue}> <CounterDisplay /> <CounterControls /> </CountContext.Provider> ); }
|
4.2 选择性 Context
function App() { const [count, setCount] = useState(0); const [theme, setTheme] = useState('light');
return ( <Context.Provider value={{ count, setCount, theme, setTheme }}> <CounterDisplay /> <ThemeDisplay /> </Context.Provider> ); }
function App() { const countContext = useMemo(() => ({ count, setCount }), [count, setCount]); const themeContext = useMemo(() => ({ theme, setTheme }), [theme, setTheme]);
return ( <CountContext.Provider value={countContext}> <ThemeContext.Provider value={themeContext}> <CounterDisplay /> <ThemeDisplay /> </ThemeContext.Provider> </CountContext.Provider> ); }
|
4.3 Context Consumer
function MyComponent() { return ( <ThemeContext.Consumer> {theme => ( <div style={{ color: theme }}> 内容 </div> )} </ThemeContext.Consumer> ); }
function MyComponent() { const theme = useContext(ThemeContext); return <div style={{ color: theme }}>内容</div>; }
|
五、Context vs Redux
5.1 Context 的优势
- 简单易用:无需额外库
- 原生支持:React 内置
- 低开销:适合简单场景
5.2 Context 的局限性
- 性能:大型应用重渲染问题
- 调试:难以调试
- 工具支持:缺少可视化工具
5.3 何时使用 Context
function App() { return ( <ThemeProvider> <AuthContext.Provider value={{ user, login, logout }}> <I18nContext.Provider value={{ lang, t, changeLang }}> <Navbar /> <Content /> </I18nContext.Provider> </AuthContext.Provider> </ThemeProvider> ); }
function App() { return ( <Provider store={store}> <Routes /> </Provider> ); }
|
六、最佳实践
6.1 命名规范
const UserContext = createContext(); const ThemeContext = createContext(); const I18nContext = createContext();
const ctx = createContext(); const context = createContext();
|
6.2 错误处理
function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme 必须在 ThemeProvider 中使用'); } return context; }
|
6.3 按需提供
function App() { const [theme, setTheme] = useState('light'); const [user, setUser] = useState(null);
return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Navbar /> {user && ( <UserContext.Provider value={{ user, setUser }}> <Content /> </UserContext.Provider> )} </ThemeContext.Provider> ); }
|
七、总结
7.1 Context 的核心要点
- 提供全局状态:避免 props 逐层传递
- 简单易用:无需额外库
- 性能考虑:避免过度使用
7.2 常见应用场景
- 主题切换
- 用户认证
- 国际化(i18n)
- 主题配置
7.3 最佳实践
- 合理使用:只在必要时使用 Context
- 性能优化:减少不必要的重渲染
- 错误处理:添加错误提示
- 命名规范:使用有意义的名称
掌握 Context,简化组件树的状态管理!