说说 React 中 useState 和 useReducer 的区别?
问题解析
面试官考察点:
- 是否理解 useState 和 useReducer 的关系
- 能否根据场景选择合适的状态管理方式
- 对 Redux 思想的理解
- 状态管理的最佳实践
核心概念
useState 和 useReducer 的关系
useState 实际上是 useReducer 的语法糖。当你使用 useState 时,React 内部使用了一个简单的 reducer 来处理状态更新。
// useState 的简单实现
function useState(initialValue) {
return useReducer(
(state, action) => action,
initialValue
);
}
详细解答
1. useState 基本用法
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(prev => prev - 1)}>-</button>
<input
value={user.name}
onChange={(e) => setUser({ ...user, name: e.target.value })}
/>
</div>
);
}
2. useReducer 基本用法
import React, { useReducer } from 'react';
// 定义 reducer
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
case 'reset':
return initialState;
default:
throw new Error('Unknown action type');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<p>Step: {state.step}</p>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
<input
type="number"
value={state.step}
onChange={(e) => dispatch({
type: 'setStep',
payload: Number(e.target.value)
})}
/>
</div>
);
}
3. 复杂状态逻辑对比
useState 处理复杂状态
function TodoList() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const [nextId, setNextId] = useState(1);
// 添加 todo - 需要处理多个状态
const addTodo = (text) => {
setTodos([...todos, { id: nextId, text, completed: false }]);
setNextId(nextId + 1);
};
// 切换完成状态
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
// 删除 todo
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// 过滤逻辑分散在各个地方
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
// ... 渲染逻辑
}
useReducer 处理复杂状态
const initialState = {
todos: [],
filter: 'all',
nextId: 1
};
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return {
...state,
todos: [...state.todos, {
id: state.nextId,
text: action.payload,
completed: false
}],
nextId: state.nextId + 1
};
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)
};
case 'setFilter':
return { ...state, filter: action.payload };
default:
throw new Error('Unknown action type');
}
}
function TodoList() {
const [state, dispatch] = useReducer(todoReducer, initialState);
// 逻辑清晰,所有状态变更集中在 reducer
const addTodo = (text) => dispatch({ type: 'add', payload: text });
const toggleTodo = (id) => dispatch({ type: 'toggle', payload: id });
const deleteTodo = (id) => dispatch({ type: 'delete', payload: id });
// 过滤逻辑也可以在 reducer 中处理
const filteredTodos = state.todos.filter(todo => {
if (state.filter === 'active') return !todo.completed;
if (state.filter === 'completed') return todo.completed;
return true;
});
// ... 渲染逻辑
}
4. 懒初始化
// useState 懒初始化
function Counter({ initialCount }) {
// 使用函数避免每次渲染都计算初始值
const [count, setCount] = useState(() => {
console.log('Lazy initialization');
return computeExpensiveValue(initialCount);
});
}
// useReducer 懒初始化
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(
reducer,
initialCount,
init // 初始化函数
);
}
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
default:
throw new Error();
}
}
深入理解
状态更新方式对比
// useState - 直接设置新值
setCount(5);
setCount(prev => prev + 1); // 基于前一个值
// useReducer - 通过 action 描述更新
dispatch({ type: 'set', payload: 5 });
dispatch({ type: 'increment' }); // reducer 内部处理逻辑
何时使用 useReducer
- 状态逻辑复杂:包含多个子值或状态相互依赖
- 下一个状态依赖于之前的状态
- 需要复用状态逻辑:可以将 reducer 函数提取到组件外
- 状态更新逻辑复杂:需要处理多种操作类型
// 适合 useReducer 的场景
function ComplexForm() {
const [state, dispatch] = useReducer(formReducer, {
values: {},
errors: {},
touched: {},
isSubmitting: false,
isValid: false
});
// 所有表单操作通过 dispatch 处理
const handleChange = (field, value) => {
dispatch({ type: 'field_change', field, value });
};
const handleBlur = (field) => {
dispatch({ type: 'field_blur', field });
};
const handleSubmit = async () => {
dispatch({ type: 'submit_start' });
try {
await submitForm(state.values);
dispatch({ type: 'submit_success' });
} catch (error) {
dispatch({ type: 'submit_error', error });
}
};
}
useReducer + Context 实现全局状态
// StateContext.js
const StateContext = React.createContext();
const DispatchContext = React.createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
function useAppState() {
return useContext(StateContext);
}
function useAppDispatch() {
return useContext(DispatchContext);
}
// 在组件中使用
function UserProfile() {
const state = useAppState();
const dispatch = useAppDispatch();
return (
<div>
<p>{state.user.name}</p>
<button onClick={() => dispatch({ type: 'logout' })}>
Logout
</button>
</div>
);
}
最佳实践
1. 选择指南
// 使用 useState
function SimpleComponent() {
const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState('');
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</div>
);
}
// 使用 useReducer
function ComplexComponent() {
const [state, dispatch] = useReducer(reducer, initialState);
// 状态逻辑复杂,多个相关状态
return (
<div>
<button onClick={() => dispatch({ type: 'action1' })}>Action 1</button>
<button onClick={() => dispatch({ type: 'action2' })}>Action 2</button>
<button onClick={() => dispatch({ type: 'action3' })}>Action 3</button>
</div>
);
}
2. Reducer 组织
// 将 reducer 逻辑分离到单独文件
// reducers/counter.js
export const initialState = { count: 0, step: 1 };
export function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
default:
return state;
}
}
// 组件中使用
import { counterReducer, initialState } from './reducers/counter';
function Counter() {
const [state, dispatch] = useReducer(counterReducer, initialState);
// ...
}
3. Action creators
// 定义 action creators
const actions = {
increment: () => ({ type: 'increment' }),
decrement: () => ({ type: 'decrement' }),
setStep: (step) => ({ type: 'setStep', payload: step }),
reset: () => ({ type: 'reset' })
};
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<button onClick={() => dispatch(actions.increment())}>+</button>
<button onClick={() => dispatch(actions.decrement())}>-</button>
<button onClick={() => dispatch(actions.reset())}>Reset</button>
</div>
);
}
对比总结
| 特性 | useState | useReducer |
|---|---|---|
| 适用场景 | 简单状态 | 复杂状态逻辑 |
| 状态更新 | 直接设置 | 通过 action |
| 代码组织 | 分散在组件中 | 集中在 reducer |
| 可测试性 | 一般 | 高(纯函数) |
| 学习曲线 | 低 | 中 |
| 与 Redux 关系 | - | 类似 Redux 思想 |
| 调试 | 简单 | 可记录 action 历史 |
面试要点
- 本质关系:useState 是 useReducer 的语法糖
- 选择依据:根据状态复杂度和逻辑复杂度选择
- useReducer 优势:逻辑集中、易于测试、适合复杂场景
- 实际应用:可以结合 Context 实现轻量级全局状态管理
常见追问:
- 什么时候应该使用 useReducer 而不是 useState?
- useReducer 和 Redux 有什么区别?
- 如何在 useReducer 中处理异步操作?
- useReducer 的 dispatch 是同步还是异步的?