React: Context 与全局状态
最后更新:2026-08-26
1. 你将学到
- createContext 和 Provider
- useContext 消费上下文
- Context vs Props Drilling
- 多层嵌套和性能优化
- Context + useReducer 组合(轻量状态管理)
2. 一个主题切换的故事
(1) 痛点:Props 穿透 5 层组件
Alice 要给应用加一个"黑暗模式"切换功能。数据流要穿透 5 层组件:
▶ 示例:Context使用
JSX
// 需求:用户切换黑暗模式,所有子组件都跟着变
// 第 5 层:最里层的 Button 需要知道 theme
function Button({ theme }) {
return <button style={{
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? 'white' : '#333'
}}>按钮</button>
}
// 第 4 层
function Sidebar({ theme }) {
return <div><Button theme={theme} /></div>
}
// 第 3 层
function MainLayout({ theme }) {
return <div><Sidebar theme={theme} /></div>
}
// 第 2 层
function Page({ theme }) {
return <MainLayout theme={theme} />
}
// 第 1 层:App 有 theme 状态
function App() {
const [theme, setTheme] = useState('light')
return <Page theme={theme} /> // ❌ theme 穿透 5 层
}
Alice 的问题:
theme从 App 一层层传到 Button,中间 3 层其实不需要这个数据- 如果再加一个用户信息要传递,Props 会变得极其臃肿
- 这就是经典的 Props Drilling(属性钻取)
(2) Context 的解法
JSX
// 1. 创建 Context
const ThemeContext = React.createContext('light')
// 2. Provider 提供数据
function App() {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
)
}
// 中间组件完全不关心 theme
function Page() { return <MainLayout /> }
function MainLayout() { return <Sidebar /> }
function Sidebar() { return <Button /> }
// 5. 只有需要的组件才读取 Context
function Button() {
const { theme, setTheme } = useContext(ThemeContext)
return (
<button style={{
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? 'white' : '#333'
}}
onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
{theme === 'dark' ? '🌙' : '☀️'} 切换主题
</button>
)
}
收益:中间组件(Page、MainLayout、Sidebar)不再需要传 theme Props,数据直接从 Provider 跳到 Consumer。
3. Context 的核心概念
| API | 作用 | 参数/返回值 |
|---|---|---|
createContext(defaultValue) |
创建上下文容器 | 返回 { Provider, Consumer } |
<Context.Provider value={...}> |
提供数据给子树 | value:任意类型,变化时触发 Consumer 重渲染 |
useContext(Context) |
消费最近的 Provider 数据 | 返回 Provider 的 value,无 Provider 返回默认值 |
自定义 useXxx() Hook |
封装 useContext + 错误检查 | 推荐模式,Provider 缺失时抛出明确错误 |
(1) createContext + Provider + useContext
graph TB
A[createContext<br/>创建上下文容器] --> B[Provider<br/>包裹组件树<br/>提供 value]
B --> C[中间组件<br/>不关心 Context]
C --> D[useContext<br/>消费 Context 数据]
style A fill:#1890ff,color:#fff
style B fill:#52c41a,color:#fff
style D fill:#ff6b6b,color:#fff
JSX
// 1. 创建 Context(可以给默认值)
const UserContext = createContext({ name: '访客', role: 'guest' })
const ThemeContext = createContext('light')
// 2. Provider 提供数据(包裹需要访问的组件树)
function App() {
const [user, setUser] = useState({ name: 'Alice', role: 'admin' })
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value="dark">
<Dashboard />
</ThemeContext.Provider>
</UserContext.Provider>
)
}
// 3. 任何子组件读取 Context
function Dashboard() {
const { user } = useContext(UserContext)
const theme = useContext(ThemeContext)
return <p>{user.name} - 主题:{theme}</p>
}
(2) Context 的适用场景
| 适合用 Context | 不适合用 Context |
|---|---|
| 主题(暗黑模式) | 组件配置参数(用 Props) |
| 用户登录信息 | 表单输入值(用本地 State) |
| 语言/国际化 | API 数据(用 TanStack Query) |
| 路由状态 | 动画状态(用动画库管理) |
| 全局设置 | 简单父传子(直接用 Props) |
规则:数据需要被"多个层级、多个组件"共享时才用 Context。如果只是父子组件传值,Props 就够。
4. Context 嵌套和 Provider 组件化
(1) 多层 Context 嵌套
实际项目通常有多个 Context:
JSX
function App() {
return (
<AuthProvider>
<ThemeProvider>
<I18nProvider>
<NotificationProvider>
<MainApp />
</NotificationProvider>
</I18nProvider>
</ThemeProvider>
</AuthProvider>
)
}
为了避免嵌套地狱,可以写一个 AppProviders 组合组件:
JSX
function AppProviders({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<I18nProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</I18nProvider>
</ThemeProvider>
</AuthProvider>
)
}
function App() {
return (
<AppProviders>
<MainApp />
</AppProviders>
)
}
(2) 自定义 Provider Hook
JSX
// ============================================
// 示例:自定义 ThemeProvider + useTheme Hook
// ============================================
const ThemeContext = React.createContext(null)
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState(() => {
// 从 localStorage 读取初始主题
return localStorage.getItem('theme') || 'light'
})
function toggleTheme() {
setTheme(prev => {
const next = prev === 'light' ? 'dark' : 'light'
localStorage.setItem('theme', next) // 持久化
return next
})
}
const value = React.useMemo(
() => ({ theme, toggleTheme }),
[theme]
)
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
)
}
// 自定义 Hook:封装 useContext + 错误检查
function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme 必须在 ThemeProvider 内部使用')
}
return context
}
// 使用
function ThemedButton() {
const { theme, toggleTheme } = useTheme()
return (
<button onClick={toggleTheme}
style={{
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? 'white' : '#333',
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}>
当前主题:{theme === 'dark' ? '🌙' : '☀️'} 点击切换
</button>
)
}
5. Context 性能优化
Context 的值变化时,所有消费该 Context 的组件都会重新渲染。这是 Context 的最大性能陷阱。
| 优化方式 | 原理 | 适用场景 |
|---|---|---|
useMemo 稳定 value |
避免每次渲染创建新对象引用 | Provider 的 value 包含函数或对象时 |
| 拆分 Context | "变化"与"不变"数据分到不同 Context | 部分 Consumer 只需要稳定数据 |
React.memo 包裹 Consumer |
跳过 props 未变的重渲染 | Consumer 组件接收其他 props 时 |
| 选择性订阅 | 用 selector 只取需要的切片 | 配合 Zustand/Redux 实现精确更新 |
JSX
// ❌ 问题:每次 App 渲染,value 都是新对象 → 所有 Consumer 都重渲染
function App() {
const [count, setCount] = useState(0)
return (
<ThemeContext.Provider value={{ theme: 'dark', setCount }}>
<ExpensiveTree />
</ThemeContext.Provider>
)
}
// ✅ 修复 1:用 useMemo 稳定 value 引用
function App() {
const [count, setCount] = useState(0)
const themeValue = useMemo(
() => ({ theme: 'dark', toggleTheme: () => setCount(c => c + 1) }),
[]
)
return (
<ThemeContext.Provider value={themeValue}>
<ExpensiveTree />
</ThemeContext.Provider>
)
}
// ✅ 修复 2:把"不变"和"变"的数据分开不同的 Context
const SettingsContext = createContext({ theme: 'light', language: 'zh' })
const CountContext = createContext(0)
function App() {
const [count, setCount] = useState(0)
return (
<SettingsContext.Provider value={settingsValue}> {/* useMemo 稳定 */}
<CountContext.Provider value={count}>
<ExpensiveTree />
</CountContext.Provider>
</SettingsContext.Provider>
)
}
// 只有用到 CountContext 的组件会在 count 变化时重渲染
// SettingsContext 的消费者不会受影响
6. Context + useReducer:构建轻量全局状态
结合 Context 的"跨层级传递"和 useReducer 的"复杂状态管理",可以构建一个轻量级全局状态方案:
JSX
// ============================================
// 示例:全局通知系统(Context + useReducer)
// ============================================
// ---- 1. 创建 Context ----
const NotificationContext = createContext(null)
// ---- 2. 定义 reducer ----
function notificationReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, {
id: Date.now(),
message: action.message,
type: action.notifType || 'info',
read: false
}]
case 'MARK_READ':
return state.map(n =>
n.id === action.id ? { ...n, read: true } : n
)
case 'REMOVE':
return state.filter(n => n.id !== action.id)
case 'CLEAR_ALL':
return []
default:
return state
}
}
// ---- 3. Provider 组件 ----
function NotificationProvider({ children }) {
const [notifications, dispatch] = useReducer(notificationReducer, [])
// 用 useMemo 稳定 value
const value = useMemo(() => ({ notifications, dispatch }), [notifications])
return (
<NotificationContext.Provider value={value}>
{children}
</NotificationContext.Provider>
)
}
// ---- 4. 自定义 Hook ----
function useNotification() {
const context = useContext(NotificationContext)
if (!context) throw new Error('useNotification 必须在 NotificationProvider 内使用')
return context
}
// ---- 5. 用通知的组件 ----
function NotificationBell() {
const { notifications, dispatch } = useNotification()
const unread = notifications.filter(n => !n.read).length
return (
<div style={{ position: 'relative', cursor: 'pointer' }}>
🔔
{unread > 0 && (
<span style={{
position: 'absolute', top: '-8px', right: '-8px',
backgroundColor: '#ff4d4f', color: 'white',
borderRadius: '50%', width: '18px', height: '18px',
fontSize: '12px', display: 'flex', alignItems: 'center',
justifyContent: 'center'
}}>
{unread}
</span>
)}
</div>
)
}
function NotificationList() {
const { notifications, dispatch } = useNotification()
if (notifications.length === 0) {
return <p style={{ color: '#999', textAlign: 'center' }}>暂无通知</p>
}
return (
<div style={{ maxWidth: '400px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<strong>通知({notifications.length})</strong>
<button onClick={() => dispatch({ type: 'CLEAR_ALL' })} style={smallBtn}>全部清除</button>
</div>
{notifications.map(n => (
<div key={n.id} style={{
padding: '8px', marginBottom: '4px',
backgroundColor: n.read ? '#fafafa' : '#e6f7ff',
borderRadius: '4px', cursor: 'pointer',
display: 'flex', justifyContent: 'space-between',
alignItems: 'center'
}}
onClick={() => dispatch({ type: 'MARK_READ', id: n.id })}>
<span>
{n.type === 'error' ? '❌' : n.type === 'success' ? '✅' : 'ℹ️'}
{n.message}
</span>
<button onClick={e => { e.stopPropagation(); dispatch({ type: 'REMOVE', id: n.id }) }}
style={{ ...smallBtn, color: '#ff4d4f' }}>✕</button>
</div>
))}
</div>
)
}
// ---- 发通知的组件 ----
function NotificationSender() {
const { dispatch } = useNotification()
const [message, setMessage] = useState('')
function send(type) {
if (!message.trim()) return
dispatch({ type: 'ADD', message: message.trim(), notifType: type })
setMessage('')
}
return (
<div>
<input value={message} onChange={e => setMessage(e.target.value)}
placeholder="通知内容" style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
<div style={{ display: 'flex', gap: '4px' }}>
<button onClick={() => send('info')} style={typeBtn('#1890ff')}>ℹ️ info</button>
<button onClick={() => send('success')} style={typeBtn('#52c41a')}>✅ 成功</button>
<button onClick={() => send('error')} style={typeBtn('#ff4d4f')}>❌ 错误</button>
</div>
</div>
)
}
const smallBtn = { padding: '2px 8px', fontSize: '12px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer' }
const typeBtn = (color) => ({ padding: '6px 12px', backgroundColor: color, color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' })
// ---- 6. 组装 ----
function NotificationApp() {
return (
<NotificationProvider>
<div style={{ maxWidth: '500px', margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2>🔔 通知系统</h2>
<NotificationBell />
</div>
<NotificationSender />
<hr style={{ margin: '16px 0' }} />
<h3>通知列表</h3>
<NotificationList />
</div>
</NotificationProvider>
)
}
预期输出:完整通知系统。顶部有通知铃铛(显示未读数),中间可以发 info/success/error 三种通知,下方显示通知列表,每条通知可标记已读或删除。
▶ 示例 2:用户认证状态管理(Context + 自定义 Hook)
JSX
📖 仅展示
// ============================================
// 示例:用户认证系统——Context + 自定义 Hook 的最佳实践
// 功能:管理登录/登出状态,全局提供用户信息
// ============================================
import { createContext, useContext, useState, useCallback, useMemo } from 'react'
// ---- 1. 创建 Context ----
const AuthContext = createContext(null)
// ---- 2. Provider 组件 ----
function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const login = useCallback(async (username, password) => {
setLoading(true)
try {
// 模拟登录请求
await new Promise(resolve => setTimeout(resolve, 800))
if (username && password) {
const loggedInUser = {
id: 1,
name: username,
email: `${username}@example.com`,
avatar: 'https://i.pravatar.cc/40',
role: username === 'admin' ? '管理员' : '普通用户'
}
setUser(loggedInUser)
localStorage.setItem('user', JSON.stringify(loggedInUser))
return { success: true }
}
return { success: false, error: '用户名或密码错误' }
} finally {
setLoading(false)
}
}, [])
const logout = useCallback(() => {
setUser(null)
localStorage.removeItem('user')
}, [])
// 用 useMemo 稳定 value 引用,避免 Provider 下所有子组件重渲染
const value = useMemo(() => ({
user,
loading,
isAuthenticated: !!user,
login,
logout
}), [user, loading, login, logout])
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
// ---- 3. 自定义 Hook + 错误检查 ----
function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth 必须在 AuthProvider 内部使用')
}
return context
}
// ---- 4. 使用认证的组件 ----
function LoginForm() {
const { login, loading } = useAuth()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
async function handleSubmit(e) {
e.preventDefault()
setError('')
const result = await login(username, password)
if (!result.success) {
setError(result.error)
}
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
<h2>🔐 登录</h2>
{error && <p style={{ color: '#ff4d4f', fontSize: '14px' }}>{error}</p>}
<input value={username} onChange={e => setUsername(e.target.value)}
placeholder="用户名(输入 admin 体验管理员角色)"
style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)}
placeholder="密码"
style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
<button type="submit" disabled={loading}
style={{ width: '100%', padding: '10px', backgroundColor: '#1890ff', color: 'white', border: 'none', borderRadius: '4px', cursor: loading ? 'not-allowed' : 'pointer' }}>
{loading ? '登录中...' : '登录'}
</button>
</form>
)
}
function UserDashboard() {
const { user, logout } = useAuth()
return (
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
<img src={user.avatar} alt={user.name}
style={{ width: '40px', height: '40px', borderRadius: '50%' }} />
<div>
<h3 style={{ margin: 0 }}>{user.name}</h3>
<p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>
{user.email} · {user.role}
</p>
</div>
</div>
<button onClick={logout}
style={{ padding: '8px 16px', backgroundColor: '#ff4d4f', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
退出登录
</button>
</div>
)
}
// ---- 5. 顶层组件 ----
function AuthApp() {
const { isAuthenticated } = useAuth()
return isAuthenticated ? <UserDashboard /> : <LoginForm />
}
// ---- 6. 入口 ----
function App() {
return (
<AuthProvider>
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<AuthApp />
</div>
</AuthProvider>
)
}
▶ 示例 3:多层 Provider 组合与 i18n 国际化
JSX
📖 仅展示
const I18nContext = createContext(null)
function I18nProvider({ children }) {
const [locale, setLocale] = useState('zh')
const translations = {
zh: { title: '应用标题', greeting: '你好', logout: '退出' },
en: { title: 'App Title', greeting: 'Hello', logout: 'Logout' },
}
const t = useCallback((key) => translations[locale][key] || key, [locale])
const value = useMemo(() => ({ locale, setLocale, t }), [locale, t])
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
}
function useI18n() {
const ctx = useContext(I18nContext)
if (!ctx) throw new Error('useI18n must be inside I18nProvider')
return ctx
}
function LocaleSwitcher() {
const { locale, setLocale } = useI18n()
return (
<select value={locale} onChange={e => setLocale(e.target.value)}
style={{ padding: '4px 8px', borderRadius: 4 }}>
<option value="zh">中文</option>
<option value="en">English</option>
</select>
)
}
function Greeting() {
const { t } = useI18n()
return <h2>{t('greeting')}</h2>
}
function I18nApp() {
return (
<I18nProvider>
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Greeting />
<LocaleSwitcher />
</div>
</div>
</I18nProvider>
)
}
▶ 示例 4:拆分 Context 优化性能
JSX
📖 仅展示
const UserStateContext = createContext(null)
const UserDispatchContext = createContext(null)
function UserProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const dispatch = useMemo(() => ({
login: async (name) => {
setLoading(true)
await new Promise(r => setTimeout(r, 500))
setUser({ name, role: name === 'admin' ? 'Admin' : 'User' })
setLoading(false)
},
logout: () => setUser(null),
}), [])
const stateValue = useMemo(() => ({ user, loading }), [user, loading])
return (
<UserDispatchContext.Provider value={dispatch}>
<UserStateContext.Provider value={stateValue}>
{children}
</UserStateContext.Provider>
</UserDispatchContext.Provider>
)
}
function useUserState() { return useContext(UserStateContext) }
function useUserDispatch() { return useContext(UserDispatchContext) }
function LoginButton() {
const { login } = useUserDispatch()
const { loading } = useUserState()
return <button disabled={loading} onClick={() => login('Alice')}>Login</button>
}
function UserInfo() {
const { user } = useUserState()
if (!user) return <p>Not logged in</p>
return <p>Logged in as {user.name} ({user.role})</p>
}
function SplitContextApp() {
return (
<UserProvider>
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<UserInfo />
<LoginButton />
</div>
</UserProvider>
)
}
▶ 示例 5:useContext 替代 Props Drilling 完整示例
JSX
📖 仅展示
const SettingsContext = createContext(null)
function SettingsProvider({ children }) {
const [settings, setSettings] = useState({
theme: 'light', fontSize: 14, sidebarCollapsed: false,
})
const updateSetting = useCallback((key, value) => {
setSettings(prev => ({ ...prev, [key]: value }))
}, [])
const value = useMemo(() => ({ settings, updateSetting }), [settings, updateSetting])
return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>
}
function useSettings() {
const ctx = useContext(SettingsContext)
if (!ctx) throw new Error('useSettings must be inside SettingsProvider')
return ctx
}
function Toolbar() {
const { settings, updateSetting } = useSettings()
return (
<div style={{ padding: 8, background: settings.theme === 'dark' ? '#333' : '#f0f0f0', display: 'flex', gap: 8, alignItems: 'center' }}>
<button onClick={() => updateSetting('theme', settings.theme === 'dark' ? 'light' : 'dark')}>
{settings.theme === 'dark' ? '☀️' : '🌙'}
</button>
<button onClick={() => updateSetting('sidebarCollapsed', !settings.sidebarCollapsed)}>
{settings.sidebarCollapsed ? '▶' : '◀'} Sidebar
</button>
<select value={settings.fontSize} onChange={e => updateSetting('fontSize', Number(e.target.value))}>
<option value={12}>12px</option>
<option value={14}>14px</option>
<option value={16}>16px</option>
<option value={18}>18px</option>
</select>
</div>
)
}
function Content() {
const { settings } = useSettings()
return (
<div style={{ fontSize: settings.fontSize, color: settings.theme === 'dark' ? '#eee' : '#333',
background: settings.theme === 'dark' ? '#1a1a1a' : '#fff', padding: 16 }}>
<p>Current settings:</p>
<ul>
<li>Theme: {settings.theme}</li>
<li>Font size: {settings.fontSize}px</li>
<li>Sidebar: {settings.sidebarCollapsed ? 'collapsed' : 'expanded'}</li>
</ul>
</div>
)
}
function SettingsApp() {
return (
<SettingsProvider>
<div style={{ maxWidth: 600, margin: '0 auto' }}>
<Toolbar />
<Content />
</div>
</SettingsProvider>
)
}
❓ 常见问题
Q Context 的 value 每次都是新对象,导致所有 Consumer 都重渲染怎么办?
A 用
useMemo 稳定 value 引用:const value = useMemo(() => ({ theme, toggleTheme }), [theme])。还可以把"稳定的"和"变化的"数据分到不同的 Context 中。如果优化后仍然卡顿,考虑用 Zustand(它自带精确更新,不会触发无关组件的重渲染)。Q Context 嵌套太多层了怎么办?
A ① 用 Provider 组合组件(如前面的
AppProviders)把嵌套封装起来;② 思考是否真的需要那么多 Context——有些"全局"数据其实只是"局部全局"(比如某个页面的设置),不需要放在 App 顶层的 Context 中。Q 自定义 useXxx Hook 里为什么要检查 context 是否为 null?
A 这是一个防御性编程技巧。如果开发者忘记用 Provider 包裹组件就使用 useContext,context 会是 null(如果 createContext 没有传入默认值)或产生难以调试的错误。提前抛出清晰的错误信息,比用户在浏览器控制台看到 "Cannot read property of null" 要友好得多。
Q Context 可以替代 Redux 吗?
A 小到中型项目可以,大型项目不行。Context 缺少 Redux 的三个能力:① 中间件机制(日志、异步、持久化);② 时间旅行调试(DevTools 回放状态变化);③ 选择性订阅(Context value 变化导致所有 Consumer 重渲染,Redux 可以用 selector 只订阅需要的切片)。如果你的项目需要这三个能力,用 Zustand(轻量)或 Redux Toolkit。
📖 小节
createContext→.Provider→useContext三步使用 Context- Context 解决 Props Drilling,但不解决"状态管理"问题
- 性能陷阱:Context value 变化会导致所有 Consumer 重渲染,用
useMemo优化 - 自定义
useXxxHook + 错误检查是推荐的最佳实践 Context + useReducer可以作为轻量级全局状态方案,但复杂场景还是用 Zustand/Redux
📝 作业
- 基础题(难度⭐):创建一个
LocaleProvider+useLocaleHook,提供locale(zh/en)和toggleLocale()方法,实现中英文切换。 - 进阶题(难度⭐⭐):创建一个
AuthProvider+useAuthHook,提供user、login(username, password)、logout()方法,管理用户认证状态。 - 挑战题(难度⭐⭐⭐):创建一个
CartProvider+useCartHook,使用 Context + useReducer 实现购物车,包含添加、删除、修改数量、清空功能,用useMemo优化避免不必要重渲染。