react
Context 与全局状态
By AI-Writer 9 min read
前言
Context 是 React 提供的跨组件数据传递机制,允许你在组件树中传递数据,而无需在每一层手动传递 props。当数据需要在多个层级的组件间共享时,Context 是比”层层 prop drilling”更优雅的解决方案。
Context 基础
创建 Context
jsx
import { createContext } from 'react';
// 创建 Theme Context,默认值可自定义
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {}
});
export default ThemeContext;Provider 提供者
jsx
import ThemeContext from './ThemeContext';
function App() {
const [theme, setTheme] = useState('light');
const value = {
theme,
toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light')
};
return (
// 使用 Provider 包裹组件树
<ThemeContext.Provider value={value}>
<MainPage />
</ThemeContext.Provider>
);
}消费 Context
jsx
import { useContext } from 'react';
import ThemeContext from './ThemeContext';
function Header() {
// 使用 useContext 获取 Context 值
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<header className={`theme-${theme}`}>
<h1>我的应用</h1>
<button onClick={toggleTheme}>
当前主题:{theme}
</button>
</header>
);
}Context 完整示例
主题切换系统
jsx
import { createContext, useContext, useState } from 'react';
// 1. 创建 Context
const ThemeContext = createContext();
// 2. Provider 组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const value = { theme, toggleTheme };
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 3. 自定义 Hook(简化消费)
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme 必须在 ThemeProvider 内使用');
}
return context;
}
// 4. 消费组件
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333'
}}
>
切换为 {theme === 'light' ? '深色' : '浅色'} 主题
</button>
);
}
function ThemedBox() {
const { theme } = useTheme();
return (
<div
style={{
background: theme === 'dark' ? '#222' : '#f5f5f5',
color: theme === 'dark' ? '#fff' : '#333',
padding: '2rem',
marginTop: '1rem'
}}
>
这是 {theme === 'dark' ? '深色' : '浅色'} 主题的盒子
</div>
);
}
// 5. 使用
function App() {
return (
<ThemeProvider>
<ThemeToggle />
<ThemedBox />
</ThemeProvider>
);
}Context 性能优化
Context 的一个常见问题是:当 Provider 的 value 变化时,所有消费该 Context 的组件都会重新渲染。
问题:每次 value 变化都重新渲染
jsx
function ProblematicProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// ❌ 问题:每次 theme 变化,UserProfile 也会重新渲染
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
}方案 1:分离 Context
jsx
// 分别创建两个 Context
const UserContext = createContext();
const ThemeContext = createContext();
// 分离的 Provider
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>方案 2:使用 useMemo 稳定 value
jsx
function OptimizedProvider({ children }) {
const [user, setUser] = useState(null);
const [notifications, setNotifications] = useState([]);
const userValue = useMemo(() => ({ user, setUser }), [user]);
const notificationValue = useMemo(
() => ({ notifications, addNotification: (msg) => setNotifications(prev => [...prev, msg]) }),
[notifications]
);
return (
<UserContext.Provider value={userValue}>
<NotificationContext.Provider value={notificationValue}>
{children}
</NotificationContext.Provider>
</UserContext.Provider>
);
}方案 3:分离 context 为多个文件
jsx
// contexts/ThemeContext.tsx
const ThemeContext = createContext();
export const useTheme = () => useContext(ThemeContext);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// contexts/UserContext.tsx
const UserContext = createContext();
export const useUser = () => useContext(UserContext);
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
// App.tsx
function App() {
return (
<ThemeProvider>
<UserProvider>
<Router />
</UserProvider>
</ThemeProvider>
);
}useContext 使用技巧
获取部分值
jsx
function NotificationBadge() {
// ❌ 获取整个 context(可能触发不必要渲染)
const { user, theme, notifications } = useAppContext();
// ✓ 只获取需要的内容
const { unreadCount } = useAppContext();
}组合多个 Context
jsx
function UserProfile() {
// 分别消费不同的 Context
const { user } = useUser();
const { theme } = useTheme();
if (!user) return null;
return (
<div className={`profile-${theme}`}>
<Avatar src={user.avatar} />
<span>{user.name}</span>
</div>
);
}使用 selector 模式
jsx
// 自定义 Hook:只获取部分 context 值
function useContextSelector(context, selector) {
const ctx = useContext(context);
const selected = selector(ctx);
const [, forceUpdate] = useState({});
const prevSelected = useRef(selected);
if (selected !== prevSelected.current) {
prevSelected.current = selected;
forceUpdate({});
}
return selected;
}
// 使用
function NotificationBadge() {
const unreadCount = useContextSelector(
NotificationContext,
ctx => ctx.notifications.filter(n => !n.read).length
);
return unreadCount > 0 ? <span>{unreadCount}</span> : null;
}Context 与状态提升的选择
用 Context 的场景
plaintext
✅ 全局状态:用户登录信息、主题、语言设置
✅ 跨层级传递:爷组件 → 孙组件,不需要中间组件传递
✅ 多人协作:不同团队维护不同组件,共享数据需要明确的 API用状态提升的场景
plaintext
✅ 父子通信:父组件持有状态,子组件通过 props 接收
✅ 兄弟通信:通过父组件协调
✅ 局部状态:只在少数组件中使用
✅ 简单场景:不需要 Context 的额外复杂度示例对比
jsx
// 状态提升(适合简单场景)
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<Display count={count} />
<Increment onIncrement={() => setCount(c => c + 1)} />
</>
);
}
// Context(适合跨组件共享)
function App() {
return (
<AuthProvider> {/* 全应用的用户登录状态 */}
<ThemeProvider> {/* 全应用的主题设置 */}
<Router /> {/* 路由 */}
</ThemeProvider>
</AuthProvider>
);
}完整示例:购物车状态
jsx
import { createContext, useContext, useReducer } from 'react';
// 1. 创建 Context
const CartContext = createContext();
// 2. Reducer 定义
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
const existingIndex = state.items.findIndex(
item => item.id === action.payload.id
);
if (existingIndex >= 0) {
const newItems = [...state.items];
newItems[existingIndex].quantity += 1;
return { ...state, items: newItems };
}
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: action.payload.quantity }
: item
)
};
case 'CLEAR_CART':
return { ...state, items: [] };
default:
return state;
}
};
// 3. Provider 组件
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
const addItem = (product) => dispatch({ type: 'ADD_ITEM', payload: product });
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 = {
items: state.items,
addItem,
removeItem,
updateQuantity,
clearCart,
totalItems,
totalPrice
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}
// 4. 自定义 Hook
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart 必须在 CartProvider 内使用');
}
return context;
}
// 5. 消费组件
function CartIcon() {
const { totalItems } = useCart();
return totalItems > 0 ? <span className="badge">{totalItems}</span> : null;
}
function CartPage() {
const { items, removeItem, updateQuantity, clearCart, totalPrice } = useCart();
return (
<div>
<h1>购物车</h1>
{items.length === 0 ? (
<p>购物车是空的</p>
) : (
<>
<ul>
{items.map(item => (
<li key={item.id}>
<span>{item.name}</span>
<span>¥{item.price}</span>
<input
type="number"
value={item.quantity}
onChange={e => updateQuantity(item.id, parseInt(e.target.value))}
/>
<button onClick={() => removeItem(item.id)}>删除</button>
</li>
))}
</ul>
<p>总价:¥{totalPrice.toFixed(2)}</p>
<button onClick={clearCart}>清空购物车</button>
</>
)}
</div>
);
}Context 的注意事项
Context 替换 Prop Drilling
jsx
// ❌ Prop Drilling:层层传递
function App() {
const [user] = useState({ name: 'Alice', role: 'admin' });
return <Page user={user} />;
}
function Page({ user }) {
return <Dashboard user={user} />;
}
function Dashboard({ user }) {
return <Profile user={user} />;
}
function Profile({ user }) {
return <h1>{user.name}</h1>;
}
// ✓ 使用 Context
const UserContext = createContext();
function App() {
const [user] = useState({ name: 'Alice', role: 'admin' });
return (
<UserContext.Provider value={user}>
<Page />
</UserContext.Provider>
);
}
function Profile() {
const user = useContext(UserContext);
return <h1>{user.name}</h1>;
}Context 不是 Redux
plaintext
Context 适用场景:
✅ 主题、语言、认证状态
✅ 低频更新的全局配置
✅ 简单的状态共享
Context 不适用场景:
❌ 复杂的状态逻辑(考虑用 useReducer + Context)
❌ 高频更新的数据(考虑状态管理库)
❌ 需要中间件、devtools(考虑 Redux/Zustand)小结
- Context:React 提供的跨组件数据传递机制,通过 Provider 和 useContext 配合使用
- Provider:在组件树中提供数据,使用
valueprop 传递值 - useContext:在消费组件中获取 Context 值
- 性能优化:分离 Context、使用 useMemo 稳定 value、只消费需要的部分
- 自定义 Hook:封装 useContext,提供更友好的 API
- 选择原则:跨多层组件共享用 Context,父子通信用 props
掌握了 Context 后,下一篇文章我们将学习表单处理,了解如何在 React 中优雅地管理表单状态。
#react
#Context
#useContext
#全局状态
#进阶
评论
A
Written by
AI-Writer