useReducer 与 Redux 对比

分析 useReducer 和 Redux 的区别,以及各自适用场景

问题

useReducer 能代替 Redux 吗?它们有什么区别?

解答

简短回答

不能完全代替。useReducer 只解决了状态管理的一部分问题,Redux 提供了完整的状态管理方案。

useReducer 基本用法

import { useReducer } from 'react';

// reducer 函数
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

useReducer + Context 实现跨组件状态共享

import { createContext, useContext, useReducer } from 'react';

// 创建 Context
const StoreContext = createContext(null);

// reducer
function appReducer(state, action) {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload };
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    default:
      return state;
  }
}

// Provider 组件
function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, {
    user: null,
    todos: []
  });

  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
}

// 自定义 Hook
function useStore() {
  const context = useContext(StoreContext);
  if (!context) {
    throw new Error('useStore must be used within StoreProvider');
  }
  return context;
}

// 使用
function UserProfile() {
  const { state, dispatch } = useStore();
  
  return (
    <div>
      <p>User: {state.user?.name}</p>
      <button onClick={() => dispatch({ 
        type: 'SET_USER', 
        payload: { name: 'John' } 
      })}>
        Login
      </button>
    </div>
  );
}

功能对比

功能useReducerRedux
状态管理
跨组件共享需配合 Context✅ 内置
中间件
异步处理手动实现redux-thunk/saga
DevTools
时间旅行调试
状态持久化手动实现redux-persist
性能优化需手动处理内置 selector

Redux Toolkit 示例

import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector, useDispatch } from 'react-redux';

// 创建 slice(包含 reducer 和 actions)
const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1; // RTK 使用 Immer,可以直接修改
    },
    decrement: (state) => {
      state.count -= 1;
    },
    incrementByAmount: (state, action) => {
      state.count += action.payload;
    }
  }
});

// 导出 actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 创建 store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

// 组件中使用
function Counter() {
  // useSelector 自动订阅状态变化,只在相关状态改变时重渲染
  const count = useSelector((state) => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

// App
function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

何时选择 useReducer

// 1. 组件内部复杂状态
function Form() {
  const [state, dispatch] = useReducer(formReducer, {
    name: '',
    email: '',
    errors: {},
    isSubmitting: false
  });
  // 状态逻辑集中在 reducer 中,组件更清晰
}

// 2. 状态更新逻辑复杂
function reducer(state, action) {
  switch (action.type) {
    case 'SUBMIT_START':
      return { ...state, isSubmitting: true, errors: {} };
    case 'SUBMIT_SUCCESS':
      return { ...state, isSubmitting: false, data: action.payload };
    case 'SUBMIT_ERROR':
      return { ...state, isSubmitting: false, errors: action.payload };
    // 多个 action 修改多个字段,用 reducer 更清晰
  }
}

何时选择 Redux

// 1. 需要中间件处理异步
const fetchUser = (id) => async (dispatch) => {
  dispatch({ type: 'FETCH_START' });
  try {
    const user = await api.getUser(id);
    dispatch({ type: 'FETCH_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'FETCH_ERROR', payload: error });
  }
};

// 2. 需要 DevTools 调试
// Redux DevTools 可以查看每个 action、状态变化、时间旅行

// 3. 大型应用,多个模块共享状态
const store = configureStore({
  reducer: {
    user: userReducer,
    products: productsReducer,
    cart: cartReducer,
    orders: ordersReducer
  }
});

关键点

  • useReducer 是组件级方案:适合单组件或小范围状态管理
  • Redux 是应用级方案:提供中间件、DevTools、状态持久化等完整生态
  • useReducer + Context 有性能问题:Context 值变化会导致所有消费者重渲染
  • Redux 的 useSelector 自带性能优化:只在选中的状态变化时重渲染
  • 小项目用 useReducer:简单、无依赖、够用
  • 大项目用 Redux Toolkit:功能完整、调试方便、生态成熟