普通视图

发现新文章,点击刷新页面。
今天 — 2025年8月23日首页

Redux vs Redux Toolkit 从传统 Redux 优雅升级

作者 海海思思
2025年8月23日 10:51

Redux Toolkit (RTK) 是 Redux 官方推荐的、用于高效、简洁地编写 Redux 逻辑的现代工具集。它旨在解决 Redux 原始写法中常见的“样板代码过多”、“配置复杂”和“新手入门门槛高”等问题,通过提供一组经过良好实践验证的工具和抽象,让开发者能够更轻松地构建 Redux 应用。

Redux Toolkit 核心目标

  1. 减少样板代码 (Boilerplate): 自动生成 action creators、action types 和 immutable state updates。
  2. 提供合理的默认配置: 内置了 redux-thunk(异步逻辑)和 immer(不可变更新),开箱即用。
  3. 简化 Store 配置: 通过 configureStore 简化 store 创建过程。
  4. 包含现代 JS 工具: 集成了 createSlicecreateAsyncThunk 等高级 API。

Redux Toolkit vs. 传统 Redux 的优势

特性/方面 传统 Redux Redux Toolkit (RTK) 优势说明
代码量 大量样板代码 (types, action creators, reducers) 极大减少样板代码 createSlice 自动创建 types, actions, reducer,代码更简洁、易维护。
状态更新 手动编写 immutable 逻辑 (展开运算符) 使用 immer,可直接写“可变”逻辑 开发者无需手动处理深拷贝,逻辑更直观,减少错误。
Store 配置 手动组合 reducers,手动应用中间件 configureStore 自动合并 reducer,配置 devtools 配置更简单、更健壮,默认开启 Redux DevTools。
异步逻辑 依赖中间件 (如 redux-thunk),需手动集成 内置 createAsyncThunk,更结构化处理异步 createAsyncThunk 自动生成 pending/fulfilled/rejected actions,逻辑更清晰。
不可变性 开发者完全负责 内置 immer,自动处理不可变更新 降低因错误的 mutable 操作导致 bug 的风险。
学习曲线 相对陡峭,概念多 更平缓,API 设计更符合直觉 新手更容易上手,核心概念集中在 createSliceconfigureStore

实际业务案例:用户管理模块

假设我们需要构建一个用户管理模块,功能包括:

  • 获取用户列表 (异步)
  • 添加用户 (异步)
  • 更新用户状态 (同步)
  • 删除用户 (同步)

1. 传统 Redux 写法 (繁琐)

// actionTypes.js
export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST';
export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS';
export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE';
export const ADD_USER = 'ADD_USER';
export const UPDATE_USER_STATUS = 'UPDATE_USER_STATUS';
export const DELETE_USER = 'DELETE_USER';

// actions.js
import * as api from '../api/userApi';
import {
  FETCH_USERS_REQUEST,
  FETCH_USERS_SUCCESS,
  FETCH_USERS_FAILURE,
  ADD_USER,
  UPDATE_USER_STATUS,
  DELETE_USER,
} from './actionTypes';

// 同步 Actions
export const updateUserStatus = (userId, status) => ({
  type: UPDATE_USER_STATUS,
  payload: { userId, status },
});

export const deleteUser = (userId) => ({
  type: DELETE_USER,
  payload: { userId },
});

// 异步 Actions (Thunk)
export const fetchUsers = () => {
  return (dispatch) => {
    dispatch({ type: FETCH_USERS_REQUEST });
    api
      .getUsers()
      .then((users) => {
        dispatch({ type: FETCH_USERS_SUCCESS, payload: users });
      })
      .catch((error) => {
        dispatch({ type: FETCH_USERS_FAILURE, error: error.message });
      });
  };
};

export const addUser = (userData) => {
  return (dispatch) => {
    // 假设 API 返回新用户对象
    api
      .createUser(userData)
      .then((newUser) => {
        dispatch({ type: ADD_USER, payload: newUser });
      })
      .catch((error) => {
        // 通常需要 dispatch 一个错误 action 或在组件中处理
        console.error('Failed to add user:', error);
      });
  };
};

// reducer.js
import {
  FETCH_USERS_REQUEST,
  FETCH_USERS_SUCCESS,
  FETCH_USERS_FAILURE,
  ADD_USER,
  UPDATE_USER_STATUS,
  DELETE_USER,
} from './actionTypes';

const initialState = {
  users: [],
  loading: false,
  error: null,
};

const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USERS_REQUEST:
      return {
        ...state,
        loading: true,
        error: null,
      };
    case FETCH_USERS_SUCCESS:
      return {
        ...state,
        loading: false,
        users: action.payload, // 假设 payload 是用户数组
      };
    case FETCH_USERS_FAILURE:
      return {
        ...state,
        loading: false,
        error: action.error,
      };
    case ADD_USER:
      return {
        ...state,
        users: [...state.users, action.payload], // 手动 immutable
      };
    case UPDATE_USER_STATUS:
      return {
        ...state,
        users: state.users.map((user) =>
          user.id === action.payload.userId
            ? { ...user, status: action.payload.status }
            : user
        ),
      };
    case DELETE_USER:
      return {
        ...state,
        users: state.users.filter((user) => user.id !== action.payload.userId),
      };
    default:
      return state;
  }
};

export default userReducer;

// store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import userReducer from './reducers/userReducer';

const rootReducer = combineReducers({
  users: userReducer,
  // ... 其他 reducers
});

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

export default store;

问题:代码量大,分散在多个文件,action types 容易出错,reducer 中的 immutable 逻辑繁琐。

2. Redux Toolkit 写法 (简洁高效)

// features/users/userSlice.js
import { createSlice, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';
import * as api from '../../api/userApi';

// 使用 Entity Adapter 管理标准化的实体状态 (推荐用于列表)
export const usersAdapter = createEntityAdapter();

// 定义异步 thunk
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers', // action type 前缀
  async (_, { rejectWithValue }) => {
    try {
      const response = await api.getUsers();
      return response.data; // 返回的数据会成为 action.payload
    } catch (error) {
      return rejectWithValue(error.message); // 处理错误
    }
  }
);

export const addUser = createAsyncThunk(
  'users/addUser',
  async (userData, { rejectWithValue }) => {
    try {
      const response = await api.createUser(userData);
      return response.data; // 新用户对象
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

// 创建 slice
const usersSlice = createSlice({
  name: 'users',
  initialState: usersAdapter.getInitialState({
    // adapter 管理 entities 和 ids
    loading: 'idle', // 'idle' | 'pending' | 'succeeded' | 'failed'
    error: null,
  }),
  reducers: {
    // 同步 reducer - 使用 Immer,可直接“修改” state
    updateUserStatus(state, action) {
      const { userId, status } = action.payload;
      // 直接“修改”实体,immer 会自动处理不可变性
      const user = state.entities[userId];
      if (user) {
        user.status = status; // 这行代码在底层会被转换为不可变更新
      }
    },
    // Entity Adapter 提供的预置 reducer
    // removeOne, updateOne 等已由 adapter 提供,无需手动写
  },
  extraReducers: (builder) => {
    // 处理异步 thunk 生成的 actions
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.loading = 'pending';
        state.error = null;
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = 'succeeded';
        // 使用 adapter 的 setAll 来替换所有用户
        usersAdapter.setAll(state, action.payload);
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = 'failed';
        state.error = action.payload; // 使用 rejectWithValue 时 payload 是错误信息
      })
      // 处理 addUser
      .addCase(addUser.fulfilled, (state, action) => {
        // 使用 adapter 的 addOne 添加新用户
        usersAdapter.addOne(state, action.payload);
      })
      // 可以添加 .addCase(addUser.rejected, ...) 处理失败
      ;
  },
});

// 导出 actions (同步和异步生成的)
export const { updateUserStatus } = usersSlice.actions;

// 导出 reducer
export default usersSlice.reducer;

// selectors - 使用 adapter 的 selectors
export const {
  selectAll: selectAllUsers,
  selectById: selectUserById,
  selectIds: selectUserIds,
} = usersAdapter.getSelectors((state) => state.users);

// 也可以创建自定义 selector
export const selectUsersLoading = (state) => state.users.loading;
export const selectUsersError = (state) => state.users.error;

// store.js (RTK 版本)
import { configureStore } from '@reduxjs/toolkit';
import usersReducer from './features/users/userSlice';

export const store = configureStore({
  reducer: {
    users: usersReducer,
    // ... 其他 slices
  },
  // devTools: true, // 默认开启
  // middleware: getDefaultMiddleware => getDefaultMiddleware(), // 默认包含 thunk, immer
});

// 通常还会导出 RootState 和 AppDispatch 类型 (TypeScript)
// export type RootState = ReturnType<typeof store.getState>
// export type AppDispatch = typeof store.dispatch

优势总结 (通过案例体现)

  1. 代码量锐减

    • 传统: actionTypes.js, actions.js (多个文件), reducer.js, store.js
    • RTK: 主要逻辑集中在 userSlice.js 一个文件 (createSlice + createAsyncThunk),store.js 极简。
  2. 自动处理样板

    • createSlice 自动根据 reducersextraReducers 中的 case 生成 action types 和 action creators。
    • createAsyncThunk 自动为 pending, fulfilled, rejected 状态生成 action types 和 creators。
  3. Immer 简化不可变更新

    • updateUserStatus reducer 中,直接写 user.status = status,而不是复杂的展开运算符。RTK 内部使用 Immer 库将其转换为不可变更新。
  4. 结构化异步处理

    • createAsyncThunk 将异步逻辑封装得更好,extraReducers 集中处理其生成的三种状态,逻辑清晰。
  5. Entity Adapter 优化实体管理

    • 对于用户列表这种常见场景,createEntityAdapter 提供了 setAll, addOne, updateOne, removeOne 等高效且标准化的 CRUD 操作,避免手动编写 mapfilter
  6. 简化 Store 配置

    • configureStore 自动合并 reducer,自动应用 redux-thunkimmer 中间件,并默认开启 Redux DevTools 扩展,无需手动配置。

结论

Redux Toolkit 并非取代 Redux,而是构建在 Redux 之上的一套最佳实践和工具集。它极大地提升了开发效率和代码可维护性,同时保留了 Redux 的核心原则(单一状态树、可预测的状态更新)。对于新项目,强烈推荐直接使用 Redux Toolkit,它可以让你专注于业务逻辑,而不是 Redux 的样板代码和配置细节。上面的用户管理案例清晰地展示了从“繁琐”到“简洁优雅”的转变。

❌
❌