useReducer
是 React 中用于处理复杂状态逻辑的 Hook,它基于 Redux 的设计理念。让我们通过实例深入理解:
1. 基本语法和工作原理
import React, { useReducer } from 'react';
// 定义 reducer 函数
const counterReducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
};
// 初始状态
const initialState = { count: 0 };
function Counter() {
const [state, dispatch] = useReducer(counterReducer, 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>
);
}
2. 处理复杂状态逻辑
import React, { useReducer } from 'react';
// 定义 action types
const ACTIONS = {
ADD_TODO: 'ADD_TODO',
TOGGLE_TODO: 'TOGGLE_TODO',
DELETE_TODO: 'DELETE_TODO',
UPDATE_TODO: 'UPDATE_TODO'
};
// 定义 reducer
const todoReducer = (state, action) => {
switch (action.type) {
case ACTIONS.ADD_TODO:
return {
...state,
todos: [
...state.todos,
{
id: Date.now(),
text: action.payload,
completed: false
}
]
};
case ACTIONS.TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case ACTIONS.DELETE_TODO:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case ACTIONS.UPDATE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id
? { ...todo, text: action.payload.text }
: todo
)
};
default:
return state;
}
};
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, { todos: [] });
const [input, setInput] = React.useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim()) {
dispatch({ type: ACTIONS.ADD_TODO, payload: input });
setInput('');
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo"
/>
<button type="submit">Add</button>
</form>
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
onClick={() => dispatch({
type: ACTIONS.TOGGLE_TODO,
payload: todo.id
})}
>
{todo.text}
</span>
<button
onClick={() => dispatch({
type: ACTIONS.DELETE_TODO,
payload: todo.id
})}
>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
3. 使用 useReducer 处理异步操作
import React, { useReducer, useEffect } from 'react';
const dataReducer = (state, action) => {
switch (action.type) {
case 'FETCH_START':
return {
...state,
isLoading: true,
error: null
};
case 'FETCH_SUCCESS':
return {
isLoading: false,
error: null,
data: action.payload
};
case 'FETCH_ERROR':
return {
isLoading: false,
error: action.payload,
data: null
};
default:
return state;
}
};
function DataFetcher({ url }) {
const [state, dispatch] = useReducer(dataReducer, {
isLoading: false,
error: null,
data: null
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch(url);
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
};
fetchData();
}, [url]);
if (state.isLoading) return <div>Loading...</div>;
if (state.error) return <div>Error: {state.error}</div>;
if (!state.data) return null;
return (
<div>
{/* 渲染数据 */}
<pre>{JSON.stringify(state.data, null, 2)}</pre>
</div>
);
}
4. useReducer vs useState 对比
- 使用 useState 的场景:
function SimpleCounter() {
const [count, setCount] = useState(0);
// 简单状态更新
const increment = () => setCount(count + 1);
return (
<button onClick={increment}>
Count: {count}
</button>
);
}
- 使用 useReducer 的场景:
function ComplexForm() {
const [formState, dispatch] = useReducer(formReducer, {
username: '',
password: '',
email: '',
errors: {},
isSubmitting: false
});
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: 'SUBMIT_START' });
try {
await submitForm(formState);
dispatch({ type: 'SUBMIT_SUCCESS' });
} catch (error) {
dispatch({ type: 'SUBMIT_ERROR', payload: error.errors });
}
};
return (
<form onSubmit={handleSubmit}>
{/* 表单内容 */}
</form>
);
}
选择 useReducer 的情况:
-
状态逻辑复杂:
- 状态包含多个子值
- 状态更新涉及复杂的逻辑
- 需要基于之前的状态进行更新
-
相关状态更新:
- 多个状态需要同时更新
- 状态更新之间有依赖关系
-
状态更新可预测:
- 状态更新遵循固定的模式
- 需要清晰的状态变更追踪
-
状态共享需求:
- 需要在深层组件树中共享状态
- 需要将状态更新逻辑提取到组件外
最佳实践:
- Action 类型常量化:
const ACTIONS = {
UPDATE_FIELD: 'UPDATE_FIELD',
VALIDATE_FORM: 'VALIDATE_FORM',
SUBMIT_FORM: 'SUBMIT_FORM',
// ...
};
- Action 创建函数:
const createUpdateAction = (field, value) => ({
type: ACTIONS.UPDATE_FIELD,
payload: { field, value }
});
- Reducer 模块化:
const reducers = {
[ACTIONS.UPDATE_FIELD]: (state, action) => ({
...state,
[action.payload.field]: action.payload.value
}),
// ...其他 reducers
};
const mainReducer = (state, action) => {
const handler = reducers[action.type];
return handler ? handler(state, action) : state;
};
useReducer 是一个强大的状态管理工具,特别适合处理复杂的状态逻辑。它提供了更清晰的状态更新流程,使代码更易维护和测试。但对于简单的状态管理,useState 可能是更好的选择。选择哪个 Hook 应该基于具体的使用场景和需求。