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

153 阅读5分钟

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 的优势

特性/方面传统 ReduxRedux 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 的样板代码和配置细节。上面的用户管理案例清晰地展示了从“繁琐”到“简洁优雅”的转变。