Redux vs Redux Toolkit 从传统 Redux 优雅升级
Redux Toolkit (RTK) 是 Redux 官方推荐的、用于高效、简洁地编写 Redux 逻辑的现代工具集。它旨在解决 Redux 原始写法中常见的“样板代码过多”、“配置复杂”和“新手入门门槛高”等问题,通过提供一组经过良好实践验证的工具和抽象,让开发者能够更轻松地构建 Redux 应用。
Redux Toolkit 核心目标
- 减少样板代码 (Boilerplate): 自动生成 action creators、action types 和 immutable state updates。
-
提供合理的默认配置: 内置了
redux-thunk
(异步逻辑)和immer
(不可变更新),开箱即用。 -
简化 Store 配置: 通过
configureStore
简化 store 创建过程。 -
包含现代 JS 工具: 集成了
createSlice
、createAsyncThunk
等高级 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 设计更符合直觉 | 新手更容易上手,核心概念集中在 createSlice 和 configureStore 。 |
实际业务案例:用户管理模块
假设我们需要构建一个用户管理模块,功能包括:
- 获取用户列表 (异步)
- 添加用户 (异步)
- 更新用户状态 (同步)
- 删除用户 (同步)
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
优势总结 (通过案例体现)
-
代码量锐减:
-
传统:
actionTypes.js
,actions.js
(多个文件),reducer.js
,store.js
。 -
RTK: 主要逻辑集中在
userSlice.js
一个文件 (createSlice
+createAsyncThunk
),store.js
极简。
-
传统:
-
自动处理样板:
-
createSlice
自动根据reducers
和extraReducers
中的 case 生成 action types 和 action creators。 -
createAsyncThunk
自动为pending
,fulfilled
,rejected
状态生成 action types 和 creators。
-
-
Immer 简化不可变更新:
- 在
updateUserStatus
reducer 中,直接写user.status = status
,而不是复杂的展开运算符。RTK 内部使用 Immer 库将其转换为不可变更新。
- 在
-
结构化异步处理:
-
createAsyncThunk
将异步逻辑封装得更好,extraReducers
集中处理其生成的三种状态,逻辑清晰。
-
-
Entity Adapter 优化实体管理:
- 对于用户列表这种常见场景,
createEntityAdapter
提供了setAll
,addOne
,updateOne
,removeOne
等高效且标准化的 CRUD 操作,避免手动编写map
和filter
。
- 对于用户列表这种常见场景,
-
简化 Store 配置:
-
configureStore
自动合并 reducer,自动应用redux-thunk
和immer
中间件,并默认开启 Redux DevTools 扩展,无需手动配置。
-
结论
Redux Toolkit 并非取代 Redux,而是构建在 Redux 之上的一套最佳实践和工具集。它极大地提升了开发效率和代码可维护性,同时保留了 Redux 的核心原则(单一状态树、可预测的状态更新)。对于新项目,强烈推荐直接使用 Redux Toolkit,它可以让你专注于业务逻辑,而不是 Redux 的样板代码和配置细节。上面的用户管理案例清晰地展示了从“繁琐”到“简洁优雅”的转变。