1. useReducer 基本概念
useReducer 是 React 的一个 Hook,用于管理复杂的状态逻辑。它接收一个 reducer 函数和初始状态,返回当前状态和 dispatch 函数。
1.1 基本语法
jsx
const [state, dispatch] = useReducer(reducer, initialState, init);
- reducer: (state, action) => newState
- initialState: 初始状态
- init: (可选) 惰性初始化函数
2. 基础示例
2.1 简单计数器
jsx
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
3. 复杂状态管理示例
3.1 待办事项列表
jsx
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false
}]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
default:
return state;
}
};
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, { todos: [] });
const [input, setInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!input.trim()) return;
dispatch({ type: 'ADD_TODO', payload: input });
setInput('');
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={e => setInput(e.target.value)}
placeholder="添加待办事项"
/>
<button type="submit">添加</button>
</form>
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
onClick={() => dispatch({
type: 'TOGGLE_TODO',
payload: todo.id
})}
>
{todo.text}
</span>
<button onClick={() => dispatch({
type: 'DELETE_TODO',
payload: todo.id
})}>
删除
</button>
</li>
))}
</ul>
</div>
);
}
3.2 表单状态管理
jsx
const formReducer = (state, action) => {
switch (action.type) {
case 'SET_FIELD':
return {
...state,
[action.field]: action.value
};
case 'SET_ERROR':
return {
...state,
errors: {
...state.errors,
[action.field]: action.error
}
};
case 'RESET_FORM':
return initialState;
default:
return state;
}
};
function ComplexForm() {
const initialState = {
username: '',
email: '',
password: '',
errors: {}
};
const [state, dispatch] = useReducer(formReducer, initialState);
const handleChange = (e) => {
const { name, value } = e.target;
dispatch({
type: 'SET_FIELD',
field: name,
value
});
// 验证逻辑
if (name === 'email' && !value.includes('@')) {
dispatch({
type: 'SET_ERROR',
field: 'email',
error: '请输入有效的邮箱地址'
});
}
};
return (
<form>
<div>
<input
name="username"
value={state.username}
onChange={handleChange}
placeholder="用户名"
/>
</div>
<div>
<input
name="email"
value={state.email}
onChange={handleChange}
placeholder="邮箱"
/>
{state.errors.email && (
<span style={{ color: 'red' }}>{state.errors.email}</span>
)}
</div>
<div>
<input
name="password"
type="password"
value={state.password}
onChange={handleChange}
placeholder="密码"
/>
</div>
<button type="button" onClick={() => dispatch({ type: 'RESET_FORM' })}>
重置
</button>
</form>
);
}
4. 使用 Immer 简化 Reducer 逻辑
Immer 允许我们以更直观的方式编写 reducer,无需手动处理不可变性。
4.1 安装 Immer
bash
npm install immer
4.2 使用 Immer 重写 Todo 示例
jsx
import produce from 'immer';
const todoReducer = produce((draft, action) => {
switch (action.type) {
case 'ADD_TODO':
draft.todos.push({
id: Date.now(),
text: action.payload,
completed: false
});
break;
case 'TOGGLE_TODO':
const todo = draft.todos.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
break;
case 'DELETE_TODO':
const index = draft.todos.findIndex(t => t.id === action.payload);
if (index !== -1) {
draft.todos.splice(index, 1);
}
break;
}
});
4.3 使用 Immer 简化复杂状态更新
具体参照:[https://immerjs.github.io/immer/zh-CN/example-setstate\]
jsx
const complexReducer = produce((draft, action) => {
switch (action.type) {
case 'UPDATE_NESTED_STATE':
draft.users[action.userId].preferences.theme = action.theme;
break;
case 'ADD_ITEM_TO_ARRAY':
draft.items[action.categoryId].push(action.item);
break;
case 'UPDATE_MULTIPLE_FIELDS':
Object.assign(draft.form, action.updates);
break;
}
});
function ComplexStateComponent() {
const [state, dispatch] = useReducer(complexReducer, {
users: {},
items: {},
form: {}
});
// 使用示例
const updateTheme = (userId, theme) => {
dispatch({
type: 'UPDATE_NESTED_STATE',
userId,
theme
});
};
const addItem = (categoryId, item) => {
dispatch({
type: 'ADD_ITEM_TO_ARRAY',
categoryId,
item
});
};
}
5. useReducer 使用场景
- 复杂的状态逻辑:当组件状态逻辑复杂,包含多个值时
- 相关状态更新:当多个状态更新紧密相关时
- 状态依赖其他状态:当状态更新依赖于其他状态值时
- 深层状态更新:当需要更新深层嵌套的状态时
- 状态更新需要集中管理:当需要在一个地方管理所有状态更新逻辑时
6. 最佳实践
- Action 类型常量化
jsx
const TODO_ACTIONS = {
ADD: 'ADD_TODO',
TOGGLE: 'TOGGLE_TODO',
DELETE: 'DELETE_TODO'
};
- Action Creator 函数化
jsx
const createTodo = (text) => ({
type: TODO_ACTIONS.ADD,
payload: text
});
- 使用 TypeScript 定义类型
typescript
interface Todo {
id: number;
text: string;
completed: boolean;
}
type TodoAction =
| { type: 'ADD_TODO'; payload: string }
| { type: 'TOGGLE_TODO'; payload: number }
| { type: 'DELETE_TODO'; payload: number };
const todoReducer = (state: Todo[], action: TodoAction): Todo[] => {
// reducer 逻辑
};
- 合理拆分 Reducer
jsx
const rootReducer = (state, action) => {
return {
todos: todosReducer(state.todos, action),
user: userReducer(state.user, action),
ui: uiReducer(state.ui, action)
};
};
通过使用 useReducer 和 Immer,我们可以更好地管理复杂的状态逻辑,同时保持代码的可读性和可维护性。Immer 特别适合处理深层嵌套的状态更新,让代码更简洁直观。