返回首页

说说 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

  1. 状态逻辑复杂:包含多个子值或状态相互依赖
  2. 下一个状态依赖于之前的状态
  3. 需要复用状态逻辑:可以将 reducer 函数提取到组件外
  4. 状态更新逻辑复杂:需要处理多种操作类型
// 适合 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 历史

面试要点

  1. 本质关系:useState 是 useReducer 的语法糖
  2. 选择依据:根据状态复杂度和逻辑复杂度选择
  3. useReducer 优势:逻辑集中、易于测试、适合复杂场景
  4. 实际应用:可以结合 Context 实现轻量级全局状态管理

常见追问:

  • 什么时候应该使用 useReducer 而不是 useState?
  • useReducer 和 Redux 有什么区别?
  • 如何在 useReducer 中处理异步操作?
  • useReducer 的 dispatch 是同步还是异步的?