useContext与useReducer #
一、Context 概述 #
1.1 什么是 Context #
Context 提供了一种在组件树中共享数据的方式,无需逐层传递 props。
text
App (Provider)
├── Header
│ └── UserAvatar ← 可以访问 Context
├── Main
│ └── Profile
│ └── UserSettings ← 可以访问 Context
└── Footer
1.2 使用场景 #
- 主题配置
- 用户信息
- 语言设置
- 应用配置
二、createContext #
2.1 创建 Context #
jsx
import { createContext } from 'preact';
// 创建带默认值的 Context
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const LanguageContext = createContext('zh-CN');
2.2 Provider 组件 #
jsx
function App() {
const [theme, setTheme] = useState('dark');
const [user, setUser] = useState({ name: 'Alice' });
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
2.3 嵌套 Provider #
jsx
function App() {
return (
<ThemeContext.Provider value="dark">
<UserContext.Provider value={{ name: 'Alice' }}>
<LanguageContext.Provider value="zh-CN">
<AppContent />
</LanguageContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
三、useContext #
3.1 基本用法 #
jsx
import { useContext } from 'preact';
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button class={`btn btn-${theme}`}>
Themed Button
</button>
);
}
3.2 消费多个 Context #
jsx
function UserProfile() {
const theme = useContext(ThemeContext);
const { user } = useContext(UserContext);
const language = useContext(LanguageContext);
return (
<div class={`profile profile-${theme}`}>
<h1>{user.name}</h1>
<p>Language: {language}</p>
</div>
);
}
3.3 更新 Context #
jsx
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Header />
<Main />
</UserContext.Provider>
);
}
function LoginButton() {
const { user, setUser } = useContext(UserContext);
if (user) {
return (
<div>
<span>{user.name}</span>
<button onClick={() => setUser(null)}>Logout</button>
</div>
);
}
return (
<button onClick={() => setUser({ name: 'Alice' })}>
Login
</button>
);
}
3.4 默认值 #
jsx
const ThemeContext = createContext('light');
// 当没有 Provider 时使用默认值
function ThemedButton() {
const theme = useContext(ThemeContext);
// 如果没有 Provider,theme 为 'light'
return <button class={theme}>Button</button>;
}
四、useReducer #
4.1 基本概念 #
useReducer 是 useState 的替代方案,适合复杂状态逻辑。
jsx
const [state, dispatch] = useReducer(reducer, initialState);
4.2 基本用法 #
jsx
import { useReducer } from 'preact/hooks';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
4.3 带 Payload 的 Action #
jsx
const initialState = {
todos: []
};
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false
}]
};
case 'toggle':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'delete':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
dispatch({ type: 'add', payload: input });
setInput('');
}
};
return (
<div>
<input
value={input}
onInput={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
/>
<button onClick={addTodo}>Add</button>
<ul>
{state.todos.map(todo => (
<li
key={todo.id}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
<button onClick={() => dispatch({ type: 'toggle', payload: todo.id })}>
Toggle
</button>
<button onClick={() => dispatch({ type: 'delete', payload: todo.id })}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
4.4 惰性初始化 #
jsx
function init(initialState) {
return {
...initialState,
initialized: true
};
}
function Component({ initialState }) {
const [state, dispatch] = useReducer(reducer, initialState, init);
// ...
}
五、组合使用 #
5.1 Context + useReducer #
jsx
const TodoContext = createContext(null);
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false
}]
};
case 'toggle':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'delete':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
default:
return state;
}
}
function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, { todos: [] });
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
}
function useTodo() {
const context = useContext(TodoContext);
if (!context) {
throw new Error('useTodo must be used within TodoProvider');
}
return context;
}
// 使用
function App() {
return (
<TodoProvider>
<TodoApp />
</TodoProvider>
);
}
function TodoList() {
const { state, dispatch } = useTodo();
return (
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => dispatch({ type: 'toggle', payload: todo.id })}>
Toggle
</button>
</li>
))}
</ul>
);
}
5.2 完整示例:购物车 #
jsx
const CartContext = createContext(null);
const initialState = {
items: [],
total: 0
};
function cartReducer(state, action) {
switch (action.type) {
case 'add': {
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': {
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 items = state.items.map(item => {
if (item.id === id) {
const diff = quantity - item.quantity;
return { ...item, quantity };
}
return item;
});
const item = state.items.find(i => i.id === id);
const diff = quantity - item.quantity;
return {
items,
total: state.total + item.price * diff
};
}
case 'clear':
return initialState;
default:
return state;
}
}
function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
const actions = {
addItem: (item) => dispatch({ type: 'add', payload: item }),
removeItem: (id) => dispatch({ type: 'remove', payload: id }),
updateQuantity: (id, quantity) =>
dispatch({ type: 'update_quantity', payload: { id, quantity } }),
clearCart: () => dispatch({ type: 'clear' })
};
return (
<CartContext.Provider value={{ state, ...actions }}>
{children}
</CartContext.Provider>
);
}
function useCart() {
return useContext(CartContext);
}
// 使用
function ProductList({ products }) {
const { addItem } = useCart();
return (
<div>
{products.map(product => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addItem(product)}>
Add to Cart
</button>
</div>
))}
</div>
);
}
function CartSummary() {
const { state, removeItem, updateQuantity } = useCart();
return (
<div>
<h2>Cart ({state.items.length} items)</h2>
<ul>
{state.items.map(item => (
<li key={item.id}>
{item.name} x {item.quantity}
<button onClick={() => updateQuantity(item.id, item.quantity + 1)}>
+
</button>
<button onClick={() => updateQuantity(item.id, item.quantity - 1)}>
-
</button>
<button onClick={() => removeItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
<p>Total: ${state.total}</p>
</div>
);
}
六、最佳实践 #
6.1 拆分 Context #
jsx
// 分离状态和操作
const TodoStateContext = createContext(null);
const TodoDispatchContext = createContext(null);
function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, initialState);
return (
<TodoStateContext.Provider value={state}>
<TodoDispatchContext.Provider value={dispatch}>
{children}
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
);
}
function useTodoState() {
return useContext(TodoStateContext);
}
function useTodoDispatch() {
return useContext(TodoDispatchContext);
}
6.2 错误处理 #
jsx
function useTodo() {
const context = useContext(TodoContext);
if (context === undefined) {
throw new Error('useTodo must be used within a TodoProvider');
}
return context;
}
6.3 性能优化 #
jsx
// 使用 useMemo 缓存 value
function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, initialState);
const value = useMemo(() => ({ state, dispatch }), [state]);
return (
<TodoContext.Provider value={value}>
{children}
</TodoContext.Provider>
);
}
七、总结 #
| 要点 | 说明 |
|---|---|
| createContext | 创建 Context |
| Provider | 提供数据 |
| useContext | 消费数据 |
| useReducer | 复杂状态管理 |
| 组合使用 | 全局状态管理 |
核心原则:
- Context 用于跨组件共享数据
- useReducer 用于复杂状态逻辑
- 组合使用实现全局状态管理
- 提供自定义 Hook 简化使用
最后更新:2026-03-28