hook:useReducer
- 可替代
useState的高级方案,管理复杂的状态变量 - 适用场景: 状态逻辑复杂、涉及多个子值、下一个状态依赖之前的状态
- 核心思想: 借鉴 Redux 的 state 管理模式:
(state, action) => newState
基本语法
const [state, dispatch] = useReducer(reducer, initialState);
// reducer 函数:接收当前状态和 action,返回新状态
function reducer(state, action) {
switch (action.type) {
case "ACTION_TYPE":
return newState;
default:
return state;
}
}
// 触发状态更新
dispatch({ type: "ACTION_TYPE", payload: data });
场景代码示例
-
【场景 1】简单计数器(基础):
-
用 reducer 管理单一状态
import { useReducer } from "react"; // 定义 reducer const reducer = (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 Counter = () => { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <p>You clicked {state.count} times</p> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "reset" })}>Reset</button> </div> ); }; -
对比 useState: 这种简单场景用 useState 更简洁,但 useReducer 让逻辑更集中
-
-
【场景 2】复杂表单管理(常用):
-
管理多个输入字段的表单状态
// 定义 reducer const formReducer = (state, action) => { switch (action.type) { case "SET_FIELD": return { ...state, [action.field]: action.value, }; case "RESET_FORM": return action.initialState; case "SUBMIT": return { ...state, isSubmitting: true, }; default: return state; } }; const UserForm = () => { const initialState = { name: "", email: "", age: "", isSubmitting: false, }; const [state, dispatch] = useReducer(formReducer, initialState); const handleChange = (field) => (e) => { dispatch({ type: "SET_FIELD", field, value: e.target.value, }); }; const handleSubmit = () => { dispatch({ type: "SUBMIT" }); // 提交逻辑... }; return ( <form> <input value={state.name} onChange={handleChange("name")} placeholder="姓名" /> <input value={state.email} onChange={handleChange("email")} placeholder="邮箱" /> <input value={state.age} onChange={handleChange("age")} placeholder="年龄" /> <button onClick={handleSubmit} disabled={state.isSubmitting}> {state.isSubmitting ? "提交中..." : "提交"} </button> <button onClick={() => dispatch({ type: "RESET_FORM", initialState })} > 重置 </button> </form> ); }; -
优势: 统一管理多个字段,逻辑清晰,易于测试
-
-
【场景 3】待办列表(常用):
-
管理列表的增删改查
// 定义 reducer const todoReducer = (state, action) => { switch (action.type) { case "ADD_TODO": return { ...state, todos: [ ...state.todos, { id: Date.now(), text: action.text, completed: false }, ], }; case "TOGGLE_TODO": return { ...state, todos: state.todos.map((todo) => todo.id === action.id ? { ...todo, completed: !todo.completed } : todo ), }; case "DELETE_TODO": return { ...state, todos: state.todos.filter((todo) => todo.id !== action.id), }; case "SET_FILTER": return { ...state, filter: action.filter, }; default: return state; } }; const TodoApp = () => { const [state, dispatch] = useReducer(todoReducer, { todos: [], filter: "all", // 'all' | 'active' | 'completed' }); const [input, setInput] = useState(""); const addTodo = () => { if (input.trim()) { dispatch({ type: "ADD_TODO", text: input }); setInput(""); } }; return ( <div> <input value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={addTodo}>添加</button> <div> <button onClick={() => dispatch({ type: "SET_FILTER", filter: "all" })} > 全部 </button> <button onClick={() => dispatch({ type: "SET_FILTER", filter: "active" })} > 未完成 </button> <button onClick={() => dispatch({ type: "SET_FILTER", filter: "completed" }) } > 已完成 </button> </div> <ul> {state.todos.map((todo) => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => dispatch({ type: "TOGGLE_TODO", id: todo.id }) } /> <span style={{ textDecoration: todo.completed ? "line-through" : "none", }} > {todo.text} </span> <button onClick={() => dispatch({ type: "DELETE_TODO", id: todo.id })} > 删除 </button> </li> ))} </ul> </div> ); }; -
优势: 多种操作集中管理,状态更新逻辑清晰
-
-
【场景 4】带 payload 的 action(常用):
-
action 携带额外数据
// 定义 reducer const counterReducer = (state, action) => { switch (action.type) { case "INCREMENT": return { count: state.count + (action.payload || 1) }; case "DECREMENT": return { count: state.count - (action.payload || 1) }; case "SET": return { count: action.payload }; default: return state; } }; const Counter = () => { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button> <button onClick={() => dispatch({ type: "INCREMENT", payload: 5 })}> +5 </button> <button onClick={() => dispatch({ type: "INCREMENT", payload: 10 })}> +10 </button> <button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button> <button onClick={() => dispatch({ type: "SET", payload: 100 })}> 设为 100 </button> </div> ); }; -
优势: action 可以携带任意数据,更加灵活
-
-
【场景 5】配合 Context 使用(最佳实践):
-
在全局状态中使用 useReducer
import { createContext, useReducer, useContext } from "react"; // 定义 reducer const appReducer = (state, action) => { switch (action.type) { case "SET_USER": return { ...state, user: action.payload }; case "SET_THEME": return { ...state, theme: action.payload }; case "LOGOUT": return { ...state, user: null }; default: return state; } }; // 创建 Context const AppContext = createContext(null); // Provider 组件 export const AppProvider = ({ children }) => { const [state, dispatch] = useReducer(appReducer, { user: null, theme: "light", }); return ( <AppContext.Provider value={{ state, dispatch }}> {children} </AppContext.Provider> ); }; // 自定义 Hook export const useApp = () => { const context = useContext(AppContext); if (!context) { throw new Error("useApp 必须在 AppProvider 内部使用"); } return context; }; // 在组件中使用 const Profile = () => { const { state, dispatch } = useApp(); return ( <div> <p>用户: {state.user?.name || "未登录"}</p> <p>主题: {state.theme}</p> <button onClick={() => dispatch({ type: "SET_USER", payload: { name: "Alice" } }) } > 登录 </button> <button onClick={() => dispatch({ type: "LOGOUT" })}>退出</button> <button onClick={() => dispatch({ type: "SET_THEME", payload: state.theme === "light" ? "dark" : "light", }) } > 切换主题 </button> </div> ); }; -
优势: 结合 Context,实现类似 Redux 的全局状态管理,但更轻量
-
-
【场景 6】惰性初始化(优化):
-
初始状态需要复杂计算时,使用第三个参数
// 初始化函数 const init = (initialCount) => { console.log("初始化执行(只执行一次)"); return { count: initialCount }; }; const reducer = (state, action) => { switch (action.type) { case "INCREMENT": return { count: state.count + 1 }; case "RESET": return init(action.payload); // 重置时也可以调用 init default: return state; } }; const Counter = ({ initialCount }) => { // 第三个参数:惰性初始化函数 const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button> <button onClick={() => dispatch({ type: "RESET", payload: 0 })}> Reset </button> </div> ); }; -
适用场景: 从 localStorage 读取、复杂计算、解析大型数据
-
useReducer vs useState
| 特性 | useState | useReducer |
|---|---|---|
| 适用场景 | 简单状态 | 复杂状态、多个子值 |
| 状态更新逻辑 | 分散在组件中 | 集中在 reducer 中 |
| 可测试性 | 较难测试 | reducer 是纯函数,易于测试 |
| 代码可维护性 | 简单场景更直观 | 复杂场景更清晰 |
| 学习成本 | 低 | 中等(需要理解 reducer 概念) |
| 性能优化 | 需要多次调用 setState | 一次 dispatch,更新多个值 |
| 适合团队协作 | 一般 | 更好(逻辑统一) |
选择原则:
- 简单的独立状态(如单个数字、字符串、布尔值)→ 用 useState
- 复杂的状态对象、多个相关状态、复杂更新逻辑 → 用 useReducer
何时使用 useReducer
| 情况 | 说明 | 示例 |
|---|---|---|
| 状态逻辑复杂 | 多个子值、多种操作 | 表单、待办列表 |
| 下一个状态依赖之前的状态 | 需要基于当前状态计算新状态 | 计数器的增减 |
| 多个相关状态需要同时更新 | 避免多次 setState | 用户信息的多字段更新 |
| 想要集中管理状态更新逻辑 | 便于维护和测试 | 复杂的业务逻辑 |
| 需要在回调中触发深层更新 | 通过 dispatch 传递给子组件 | 配合 Context 使用 |
| 状态更新逻辑可能复用 | reducer 可以提取到单独文件 | 多个组件共享相同逻辑 |
Action 的最佳实践
-
使用常量定义 action type
// ✅ 推荐:使用常量,避免拼写错误 const ActionTypes = { INCREMENT: "INCREMENT", DECREMENT: "DECREMENT", RESET: "RESET", }; const reducer = (state, action) => { switch (action.type) { case ActionTypes.INCREMENT: return { count: state.count + 1 }; case ActionTypes.DECREMENT: return { count: state.count - 1 }; default: return state; } }; // 使用时 dispatch({ type: ActionTypes.INCREMENT }); -
使用 Action Creator 函数
// ✅ 推荐:封装 action 创建逻辑 const actions = { increment: (amount = 1) => ({ type: "INCREMENT", payload: amount }), decrement: (amount = 1) => ({ type: "DECREMENT", payload: amount }), reset: () => ({ type: "RESET" }), }; // 使用时 dispatch(actions.increment(5)); dispatch(actions.decrement()); -
TypeScript 类型定义
// 定义 State 类型 interface State { count: number; loading: boolean; } // 定义 Action 类型 type Action = | { type: "INCREMENT"; payload?: number } | { type: "DECREMENT"; payload?: number } | { type: "SET_LOADING"; payload: boolean } | { type: "RESET" }; // Reducer 函数 const reducer = (state: State, action: Action): State => { switch (action.type) { case "INCREMENT": return { ...state, count: state.count + (action.payload ?? 1) }; case "DECREMENT": return { ...state, count: state.count - (action.payload ?? 1) }; case "SET_LOADING": return { ...state, loading: action.payload }; case "RESET": return { count: 0, loading: false }; default: return state; } };
注意事项
-
⚠️ Reducer 必须是纯函数
// ❌ 错误:有副作用 const badReducer = (state, action) => { console.log("状态更新"); // 副作用:日志 localStorage.setItem("count", state.count); // 副作用:存储 return { count: state.count + 1 }; }; // ✅ 正确:纯函数,无副作用 const goodReducer = (state, action) => { return { count: state.count + 1 }; }; // 副作用应该在 useEffect 中处理 useEffect(() => { console.log("状态更新"); localStorage.setItem("count", state.count); }, [state.count]); -
⚠️ 不要直接修改 state
// ❌ 错误:直接修改原对象 const badReducer = (state, action) => { state.count += 1; // 不会触发重新渲染 return state; }; // ✅ 正确:返回新对象 const goodReducer = (state, action) => { return { ...state, count: state.count + 1 }; }; -
⚠️ default 分支要返回原 state
const reducer = (state, action) => { switch (action.type) { case "INCREMENT": return { count: state.count + 1 }; default: return state; // 必须返回原状态 } }; -
✅ 复杂 reducer 可以拆分
// 拆分为多个 reducer const userReducer = (state, action) => { /* ... */ }; const themeReducer = (state, action) => { /* ... */ }; // 组合 reducer const rootReducer = (state, action) => { return { user: userReducer(state.user, action), theme: themeReducer(state.theme, action), }; }; -
✅ 使用 TypeScript 获得类型安全
// 定义清晰的类型,避免拼写错误和类型错误 type Action = | { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "SET"; payload: number };