React中useReducer和useState的实现示例
一句话总结:状态简单用一个变量就能搞定 → useState;状态有关联关系、更新逻辑复杂、或需要「状态机」模式 → useReducer。别把简单问题搞复杂,也别把复杂状态硬塞进一堆 useState!
一、useReducer 和 useState 的核心区别
// useState:一个状态变量
const [count, setCount] = useState(0);
setCount(10);
setCount(prev => prev + 1);
// useReducer:状态 + 派发 action
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'increment' });
dispatch({ type: 'set', payload: 10 });
| 对比项 | useState | useReducer |
|---|---|---|
| 适用状态 | 单个独立值 | 多个关联值 / 状态机 |
| 更新方式 | 直接设值或函数式更新 | 派发 action,reducer 集中处理 |
| 更新逻辑 | 分散在组件各处 | 集中在 reducer 函数 |
| 可测试性 | 差(逻辑在组件内) | 好(reducer 是纯函数) |
| 学习成本 | 低 | 中 |
| 适用场景 | 简单状态 | 复杂状态、表单、步骤流 |
一句话:useState 是「值 + setter」,useReducer 是「状态 + action + reducer」。状态越复杂,useReducer 的优势越明显。
二、5 大高频场景 & 代码对比
① 表单状态:5 个 useState vs 1 个 useReducer
// ❌ 5 个 useState,更新逻辑分散
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const handleSubmit = async () => {
setIsLoading(true);
setErrors({});
try {
await login(username, password);
setIsSubmitted(true);
} catch (e) {
setErrors({ form: e.message });
} finally {
setIsLoading(false);
}
};
// 每个字段都要单独写 setter...
}
修复:用 useReducer 集中管理
// ✅ useReducer 集中管理,逻辑清晰
const initialState = {
username: '',
password: '',
errors: {},
isLoading: false,
isSubmitted: false,
};
function formReducer(state, action) {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value };
case 'SET_ERRORS':
return { ...state, errors: action.errors };
case 'START_SUBMIT':
return { ...state, isLoading: true, errors: {} };
case 'SUBMIT_SUCCESS':
return { ...state, isLoading: false, isSubmitted: true };
case 'SUBMIT_ERROR':
return { ...state, isLoading: false, errors: action.errors };
default:
return state;
}
}
function LoginForm() {
const [state, dispatch] = useReducer(formReducer, initialState);
const handleSubmit = async () => {
dispatch({ type: 'START_SUBMIT' });
try {
await login(state.username, state.password);
dispatch({ type: 'SUBMIT_SUCCESS' });
} catch (e) {
dispatch({ type: 'SUBMIT_ERROR', errors: { form: e.message } });
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={state.username}
onChange={e => dispatch({ type: 'SET_FIELD', field: 'username', value: e.target.value })}
/>
{/* ... */}
</form>
);
}
② 步骤流 / 状态机:多个状态互相关联
// ❌ 多个 useState 管理步骤流,状态组合可能不合法
function Checkout() {
const [step, setStep] = useState(1);
const [cart, setCart] = useState([]);
const [address, setAddress] = useState(null);
const [payment, setPayment] = useState(null);
// ❌ 可能出现 step=3 但 address=null 的非法状态
const nextStep = () => {
if (step === 1 && cart.length === 0) return;
setStep(step + 1); // ❌ 各状态独立,容易出现非法组合
};
}
修复:用 useReducer 保证状态转换的合法性
// ✅ useReducer 集中管理,状态转换有约束
const initialState = {
step: 'cart', // cart → address → payment → confirm
cart: [],
address: null,
payment: null,
};
function checkoutReducer(state, action) {
switch (action.type) {
case 'ADD_TO_CART':
return { ...state, cart: [...state.cart, action.item] };
case 'GO_TO_ADDRESS':
if (state.cart.length === 0) return state; // ✅ 购物车空不能下一步
return { ...state, step: 'address' };
case 'SET_ADDRESS':
return { ...state, address: action.address, step: 'payment' };
case 'SET_PAYMENT':
return { ...state, payment: action.payment, step: 'confirm' };
case 'BACK':
// ✅ 回退逻辑集中管理
const order = { cart: 'address', address: 'cart', payment: 'address', confirm: 'payment' };
return { ...state, step: order[state.step] };
default:
return state;
}
}
③ 多个 setState 连续调用,useState 批处理容易出错
// ❌ 多个 setState 连续调用,依赖前一个的值
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const [total, setTotal] = useState(0);
const handleIncrement = () => {
setCount(c => c + step); // ❌ 依赖 step
setTotal(t => t + step); // ❌ 逻辑分散
if (count + step > 10) {
setStep(1); // ❌ 条件判断依赖旧 count
}
};
}
修复:useReducer 一次 dispatch 搞定
// ✅ 一次 dispatch,状态原子更新
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
const newCount = state.count + state.step;
return {
count: newCount,
step: newCount > 10 ? 1 : state.step, // ✅ 集中判断
total: state.total + state.step,
};
case 'SET_STEP':
return { ...state, step: action.step };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, {
count: 0, step: 1, total: 0,
});
const handleIncrement = () => dispatch({ type: 'INCREMENT' }); // ✅ 一行搞定
}
④ 简单计数器却用了 useReducer(过度设计)
// ❌ 简单计数器用 useReducer,杀鸡用牛刀
function Counter() {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return 0;
default: return state;
}
}, 0);
return (
<div>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
{state}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
修复:简单状态用 useState
// ✅ 简单状态直接 useState
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c - 1)}>-</button>
{count}
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
⑤ useReducer 传给子组件,避免 prop drilling
// ❌ 父组件用 useReducer,但 dispatch 逐层传递
function Parent() {
const [state, dispatch] = useReducer(reducer, initialState);
return <Child dispatch={dispatch} />; // ❌ 如果层级深,一层层传
}
function Child({ dispatch }) {
return <GrandChild dispatch={dispatch} />; // ❌ prop drilling
}
修复:用 Context 共享 dispatch
// ✅ 用 Context 共享 state + dispatch
import { createContext, useContext, useReducer } from 'react';
const StoreContext = createContext(null);
function StoreProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
}
// 任意子组件直接用
function DeepChild() {
const { state, dispatch } = useContext(StoreContext);
return <button onClick={() => dispatch({ type: 'increment' })}>{state.count}</button>;
}
三、万能兜底:什么时候该切换到 useReducer
判断流程:
当前用了几个 useState? ├── 1-2 个 → 继续 useState ├── 3+ 个 → 这些状态有关联吗? │ ├── 否 → 继续 useState(或拆组件) │ └── 是 → 更新逻辑复杂吗? │ ├── 否 → 用对象 state + $patch 式更新 │ └── 是 → ✅ 用 useReducer
快速参考表:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 单个开关 / 计数器 | useState | 简单,杀鸡不用牛刀 |
| 表单(多字段 + 验证 + 提交) | useReducer | 状态关联,逻辑集中 |
| 步骤流 / 状态机 | useReducer | 状态转换有约束 |
| 分页 + 筛选 + 排序 | useReducer | 互相关联,一次 dispatch 原子更新 |
| 异步加载(loading + data + error) | useState | 3 个独立状态,或用 SWR/React Query |
| 拖拽排序(位置 + 选中 + 拖拽中) | useReducer | 状态组合复杂 |
| 主题切换 / 语言切换 | useState | 简单独立状态 |
万能模板(表单 useReducer):
import { useReducer } from 'react';
// 1. 定义初始状态
const initialState = {
values: { username: '', password: '' },
errors: {},
isLoading: false,
isSubmitted: false,
};
// 2. 定义 reducer(纯函数,可单独测试)
function formReducer(state, action) {
switch (action.type) {
case 'SET_VALUE':
return {
...state,
values: { ...state.values, [action.field]: action.value },
};
case 'SET_ERROR':
return {
...state,
errors: { ...state.errors, [action.field]: action.error },
};
case 'START_SUBMIT':
return { ...state, isLoading: true, errors: {} };
case 'SUBMIT_SUCCESS':
return { ...state, isLoading: false, isSubmitted: true };
case 'SUBMIT_ERROR':
return { ...state, isLoading: false, errors: action.errors };
case 'RESET':
return initialState;
default:
return state;
}
}
// 3. 组件里用
function LoginForm() {
const [state, dispatch] = useReducer(formReducer, initialState);
const handleChange = (field) => (e) => {
dispatch({ type: 'SET_VALUE', field, value: e.target.value });
};
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: 'START_SUBMIT' });
try {
await login(state.values);
dispatch({ type: 'SUBMIT_SUCCESS' });
} catch (err) {
dispatch({ type: 'SUBMIT_ERROR', errors: { form: err.message } });
}
};
return (
<form onSubmit={handleSubmit}>
<input value={state.values.username} onChange={handleChange('username')} />
<input type="password" value={state.values.password} onChange={handleChange('password')} />
{state.errors.form && <span>{state.errors.form}</span>}
<button disabled={state.isLoading}>
{state.isLoading ? '提交中...' : '登录'}
</button>
</form>
);
}
四、预防 checklist
- 简单状态(1-2 个变量)用 useState,不要过度设计
- 3+ 个关联状态 或 状态转换有约束 → 用 useReducer
- 状态更新逻辑集中 在 reducer 里,组件只负责 dispatch
- reducer 必须是纯函数:不写副作用,不调 API,只返回新 state
- 异步操作放在组件或 action 函数里,reducer 只处理同步状态转换
- 传 dispatch 给子组件用 Context,避免 prop drilling
- 给 action.type 用常量(大型项目),小型项目直接字符串就行
- reducer 的 default 分支必须返回 state,不能返回 undefined
- useReducer 适合可测试场景:reducer 抽出来,单元测试直接调
五、一句话总结
「useState 管简单值,useReducer 管复杂状态机」。1-2 个独立状态用 useState,3+ 个关联状态或状态转换有约束就上 useReducer。reducer 是纯函数,逻辑集中、可测试、状态原子更新。别把简单问题搞复杂,也别把复杂状态硬塞进一堆 useState!
到此这篇关于React中useReducer和useState的实现示例的文章就介绍到这了,更多相关React useReducer useState内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
React memo允许你的组件在 props 没有改变的情况下跳过重新渲染的问题记录
使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染,这篇文章主要介绍了React memo允许你的组件在 props 没有改变的情况下跳过重新渲染,需要的朋友可以参考下2024-06-06


最新评论