Context API #
一、Context概述 #
1.1 什么是Context #
Context 提供了一种在组件树中共享数据的方式,无需手动逐层传递 props。
1.2 使用场景 #
| 场景 | 说明 |
|---|---|
| 主题切换 | 全局主题样式 |
| 用户信息 | 登录状态、用户数据 |
| 语言设置 | 国际化 |
| 全局配置 | 应用配置信息 |
1.3 解决的问题 #
javascript
// ❌ Prop Drilling问题
function App() {
const user = { name: 'Alice' };
return <Header user={user} />;
}
function Header({ user }) {
return <Navigation user={user} />;
}
function Navigation({ user }) {
return <UserMenu user={user} />;
}
function UserMenu({ user }) {
return <span>{user.name}</span>;
}
// ✅ 使用Context解决
function App() {
return (
<UserContext.Provider value={{ name: 'Alice' }}>
<Header />
</UserContext.Provider>
);
}
function UserMenu() {
const user = useContext(UserContext);
return <span>{user.name}</span>;
}
二、基本使用 #
2.1 创建Context #
javascript
import { createContext } from 'react';
// 创建Context,可以设置默认值
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const ConfigContext = createContext({ theme: 'light', lang: 'zh' });
2.2 Provider提供数据 #
javascript
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: 'Alice', role: 'admin' });
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
2.3 消费Context #
javascript
import { useContext } from 'react';
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button className={`btn btn-${theme}`}>
Themed Button
</button>
);
}
function UserProfile() {
const { user, setUser } = useContext(UserContext);
return (
<div>
<span>{user.name}</span>
<button onClick={() => setUser(null)}>退出</button>
</div>
);
}
三、Context最佳实践 #
3.1 创建Context文件 #
javascript
// contexts/ThemeContext.jsx
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(undefined);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
3.2 使用自定义Provider #
javascript
import { ThemeProvider } from './contexts/ThemeContext';
import { UserProvider } from './contexts/UserContext';
function App() {
return (
<ThemeProvider>
<UserProvider>
<Layout />
</UserProvider>
</ThemeProvider>
);
}
function ThemedComponent() {
const { theme, toggleTheme } = useTheme();
return (
<div className={theme}>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
3.3 分离Context #
javascript
// 分离状态和dispatch,优化性能
const UserStateContext = createContext();
const UserDispatchContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserStateContext.Provider value={user}>
<UserDispatchContext.Provider value={setUser}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
}
export function useUserState() {
const context = useContext(UserStateContext);
if (context === undefined) {
throw new Error('useUserState must be used within UserProvider');
}
return context;
}
export function useUserDispatch() {
const context = useContext(UserDispatchContext);
if (context === undefined) {
throw new Error('useUserDispatch must be used within UserProvider');
}
return context;
}
四、完整示例 #
4.1 用户认证Context #
javascript
// contexts/AuthContext.jsx
import { createContext, useContext, useState, useCallback } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(() => {
const saved = localStorage.getItem('user');
return saved ? JSON.parse(saved) : null;
});
const login = useCallback(async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const data = await response.json();
setUser(data.user);
localStorage.setItem('user', JSON.stringify(data.user));
return data;
}, []);
const logout = useCallback(() => {
setUser(null);
localStorage.removeItem('user');
}, []);
const value = {
user,
login,
logout,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
4.2 使用认证Context #
javascript
function App() {
return (
<AuthProvider>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</Router>
</AuthProvider>
);
}
function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return children;
}
function Login() {
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (credentials) => {
await login(credentials);
navigate('/dashboard');
};
return <LoginForm onSubmit={handleSubmit} />;
}
function Header() {
const { user, logout } = useAuth();
return (
<header>
<span>{user?.name}</span>
<button onClick={logout}>退出</button>
</header>
);
}
4.3 购物车Context #
javascript
// contexts/CartContext.jsx
import { createContext, useContext, useReducer, useCallback } from 'react';
const CartContext = createContext(null);
const initialState = {
items: [],
total: 0
};
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
const existingIndex = state.items.findIndex(
item => item.id === action.payload.id
);
if (existingIndex >= 0) {
const items = [...state.items];
items[existingIndex] = {
...items[existingIndex],
quantity: items[existingIndex].quantity + 1
};
return {
items,
total: state.total + action.payload.price
};
}
return {
items: [...state.items, { ...action.payload, quantity: 1 }],
total: state.total + action.payload.price
};
case 'REMOVE_ITEM':
const item = state.items.find(i => i.id === action.payload);
return {
items: state.items.filter(i => i.id !== action.payload),
total: state.total - (item.price * item.quantity)
};
case 'UPDATE_QUANTITY':
const { id, quantity } = action.payload;
const targetItem = state.items.find(i => i.id === id);
const diff = (quantity - targetItem.quantity) * targetItem.price;
return {
items: state.items.map(i =>
i.id === id ? { ...i, quantity } : i
),
total: state.total + diff
};
case 'CLEAR_CART':
return initialState;
default:
return state;
}
}
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
const addItem = useCallback((item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
}, []);
const removeItem = useCallback((id) => {
dispatch({ type: 'REMOVE_ITEM', payload: id });
}, []);
const updateQuantity = useCallback((id, quantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } });
}, []);
const clearCart = useCallback(() => {
dispatch({ type: 'CLEAR_CART' });
}, []);
return (
<CartContext.Provider
value={{
...state,
addItem,
removeItem,
updateQuantity,
clearCart
}}
>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within CartProvider');
}
return context;
}
五、性能优化 #
5.1 避免不必要的渲染 #
javascript
// 分离频繁变化和不频繁变化的数据
const UserContext = createContext();
const UserActionsContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const actions = useMemo(() => ({
login: async (credentials) => { /* ... */ },
logout: () => setUser(null),
updateProfile: (data) => setUser(prev => ({ ...prev, ...data }))
}), []);
return (
<UserContext.Provider value={user}>
<UserActionsContext.Provider value={actions}>
{children}
</UserActionsContext.Provider>
</UserContext.Provider>
);
}
// 只使用actions的组件不会因为user变化而重新渲染
function LoginButton() {
const { login } = useContext(UserActionsContext);
return <button onClick={() => login()}>登录</button>;
}
5.2 使用useMemo优化 #
javascript
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({
user,
setUser,
isAuthenticated: !!user
}), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
5.3 拆分Context #
javascript
// 将大型Context拆分为多个小型Context
function App() {
return (
<ThemeProvider>
<AuthProvider>
<CartProvider>
<Layout />
</CartProvider>
</AuthProvider>
</ThemeProvider>
);
}
六、常见问题 #
6.1 默认值的使用 #
javascript
// 默认值用于没有匹配Provider时
const ThemeContext = createContext('light');
// 组件在Provider外部使用时获得默认值
function Button() {
const theme = useContext(ThemeContext); // 'light'
return <button className={theme}>Button</button>;
}
// 建议:使用undefined作为默认值,便于检测
const ThemeContext = createContext(undefined);
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
6.2 Context嵌套过深 #
javascript
// ❌ 嵌套过深
<Provider1>
<Provider2>
<Provider3>
<Provider4>
<App />
</Provider4>
</Provider3>
</Provider2>
</Provider1>
// ✅ 组合Provider
function composeProviders(...providers) {
return providers.reduce((Prev, Provider) => {
return ({ children }) => (
<Prev>
<Provider>{children}</Provider>
</Prev>
);
});
}
const AppProvider = composeProviders(
ThemeProvider,
AuthProvider,
CartProvider
);
function App() {
return (
<AppProvider>
<Layout />
</AppProvider>
);
}
七、总结 #
| 要点 | 说明 |
|---|---|
| createContext | 创建Context |
| Provider | 提供数据 |
| useContext | 消费数据 |
| 自定义Hook | 封装Context访问 |
核心原则:
- 使用 Context 避免 prop drilling
- 封装 Provider 和自定义 Hook
- 分离状态和操作优化性能
- 提供有意义的错误提示
最后更新:2026-03-26