𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

React Context 实战指南

React Context 是 React 内置的状态管理方案,可以让组件树共享状态。本文将全面介绍 React Context 的核心概念、实战应用和最佳实践。

一、Context 基础

1.1 什么是 Context?

Context 提供了一种在组件树中共享数据的方式,而不需要通过 props 逐层传递。

1.2 创建 Context

import { createContext, useContext } from 'react';

// 创建 Context
const ThemeContext = createContext('light');

// Provider 组件
function ThemeProvider({ children }) {
const theme = 'dark';

return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}

// 使用 Context
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';

// 创建 Context
const ThemeContext = createContext(null);

// Provider 组件
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>
);
}

// 自定义 Hook
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';

// 创建 Context
const ThemeContext = createContext(null);

// Provider
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>
);
}

// 自定义 Hook
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';

// 创建 Context
const AuthContext = createContext(null);

// Provider
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>
);
}

// 自定义 Hook
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: '退出'
}
};

// 创建 Context
const I18nContext = createContext(null);

// Provider
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>
);
}

// 自定义 Hook
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 减少重渲染

// 不好的做法:Context 变化时所有消费者都会重渲染
function App() {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count, setCount }}>
<CounterDisplay />
<CounterControls />
</CountContext.Provider>
);
}

// 好的做法:使用 useMemo 缓存 Context
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>
);
}

// 好的做法:按需使用 Context
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 的优势

  1. 简单易用:无需额外库
  2. 原生支持:React 内置
  3. 低开销:适合简单场景

5.2 Context 的局限性

  1. 性能:大型应用重渲染问题
  2. 调试:难以调试
  3. 工具支持:缺少可视化工具

5.3 何时使用 Context

// 适合使用 Context 的场景
function App() {
return (
<ThemeProvider>
<AuthContext.Provider value={{ user, login, logout }}>
<I18nContext.Provider value={{ lang, t, changeLang }}>
<Navbar />
<Content />
</I18nContext.Provider>
</AuthContext.Provider>
</ThemeProvider>
);
}

// 适合使用 Redux 的场景
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 错误处理

// 自定义 Hook 中添加错误处理
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme 必须在 ThemeProvider 中使用');
}
return context;
}

6.3 按需提供

// 好的做法:按需提供 Context
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 的核心要点

  1. 提供全局状态:避免 props 逐层传递
  2. 简单易用:无需额外库
  3. 性能考虑:避免过度使用

7.2 常见应用场景

  1. 主题切换
  2. 用户认证
  3. 国际化(i18n)
  4. 主题配置

7.3 最佳实践

  1. 合理使用:只在必要时使用 Context
  2. 性能优化:减少不必要的重渲染
  3. 错误处理:添加错误提示
  4. 命名规范:使用有意义的名称

掌握 Context,简化组件树的状态管理!