Redux Toolkit
Redux Toolkit 概述
Redux Toolkit(RTK)是 Redux 官方推荐的状态管理方案,它大幅简化了 Redux 的使用方式。在 RTK 出现之前,Redux 的 boilerplate 代码让很多开发者望而却步;RTK 通过一系列内置工具,让 Redux 开发变得现代化和简单。
RTK 的设计理念是「 batteries included」——提供开箱即用的最佳实践,包括:
- 不可变更新逻辑
- 异步逻辑处理
- 数据获取缓存
- 开发者工具集成
createSlice
createSlice 是 RTK 的核心 API,它自动生成 reducer 和 action creators:
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
status: 'idle'
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
关键点:RTK 的 Immer 允许「可变」语法写不可变更新!
在 createSlice 的 reducers 中,你可以直接修改 state,RTK 会自动处理不可变性:
// 这段代码看起来是直接修改 state
// 但 RTK 会自动转换成不可变更新
reducers: {
increment: (state) => {
state.value += 1;
}
}
// 实际等价于(传统 Redux)
increment: (state, action) => {
return { ...state, value: state.value + 1 };
}
配置 Store
configureStore
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import userReducer from './userSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
user: userReducer,
}
});
export default store;
configureStore 自动:
- 设置 Redux DevTools
- 设置默认 middleware(包括 serializableCheck 和 thunk)
- 合并多个 reducer
传统 Redux 对比
// 传统 Redux(需要手动配置)
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
const rootReducer = combineReducers({
counter: counterReducer,
user: userReducer
});
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
React Redux Hooks
useSelector 和 useDispatch
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';
function Counter() {
// 自动类型推导
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>
Increment
</button>
</div>
);
}
性能优化:稳定的 selector
function UserProfile() {
// 每次渲染都创建新对象!会导致不必要的重新渲染
const userData = useSelector(state => ({
name: state.user.name,
email: state.user.email
}));
// 更好的做法:拆分 selector
const userName = useSelector(state => state.user.name);
const userEmail = useSelector(state => state.user.email);
// 或者使用 createSelector(来自 reselect)
const userData = useSelector(state => selectUserData(state));
}
createAsyncThunk
createAsyncThunk 用于处理异步逻辑(如 API 调用):
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { userAPI } from './api';
// createAsyncThunk 自动生成 pending/fulfilled/rejected action types
export const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId, { rejectWithValue }) => {
try {
const response = await userAPI.getUser(userId);
return response.data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const usersSlice = createSlice({
name: 'users',
initialState: {
entities: {},
loading: 'idle'
},
reducers: {
// 同步 reducers
},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = 'pending';
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.entities[action.payload.id] = action.payload;
state.loading = 'succeeded';
})
.addCase(fetchUserById.rejected, (state, action) => {
state.loading = 'failed';
state.error = action.payload;
});
}
});
使用异步 thunk
function UserProfile({ userId }) {
const dispatch = useDispatch();
const user = useSelector(state => state.users.entities[userId]);
const loading = useSelector(state => state.users.loading);
useEffect(() => {
if (loading === 'idle') {
dispatch(fetchUserById(userId));
}
}, [dispatch, userId, loading]);
if (loading === 'pending') return <Spinner />;
if (loading === 'failed') return <Error />;
if (user) return <div>{user.name}</div>;
return null;
}
RTK Query
RTK Query 是 RTK 提供的数据获取和缓存解决方案,专为 React 设计:
定义 API
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUser: builder.query({
query: (userId) => `users/${userId}`,
}),
getPosts: builder.query({
query: () => 'posts',
}),
createPost: builder.mutation({
query: (newPost) => ({
url: 'posts',
method: 'POST',
body: newPost,
}),
}),
}),
});
export const { useGetUserQuery, useGetPostsQuery, useCreatePostMutation } = api;
使用 Hooks
function UserProfile({ userId }) {
// 自动管理 loading/error/data 状态
const { data: user, isLoading, isError } = useGetUserQuery(userId);
if (isLoading) return <Spinner />;
if (isError) return <Error />;
return <div>{user.name}</div>;
}
function PostList() {
const { data: posts } = useGetPostsQuery();
const [createPost, { isLoading: isCreating }] = useCreatePostMutation();
return (
<div>
<button onClick={() => createPost({ title: 'New Post' })} disabled={isCreating}>
{isCreating ? 'Creating...' : 'Create Post'}
</button>
{posts?.map(post => <PostItem key={post.id} post={post} />)}
</div>
);
}
RTK Query 自动做的事情
- 缓存管理:相同请求不会重复发送
- 生命周期管理:组件挂载时请求,卸载时清理
- Loading 状态:自动追踪 pending 请求数量
- 乐观更新:支持
optimisticUpdate模式 - Polling:定时轮询
- Pagination:内置分页支持
缓存控制
// 强制重新获取
const { data } = useGetUserQuery(userId, { refetchOnMountOrArgChange: true });
// 当 window 重新获得焦点时重新获取
const { data } = useGetUserQuery(userId, { refetchOnFocus: true });
// 手动触发
dispatch(api.util.invalidateTags(['User']));
RTK 的 Immer 集成
不可变更新的陷阱
RTK 内部使用 Immer,允许你在 reducer 中「修改」state。但这不意味着你可以做任何操作:
// 错误:不要返回新 state,而是修改参数
reducers: {
increment: (state) => {
return { ...state, value: state.value + 1 }; // 不要这样做!
}
}
// 正确:直接修改 state
reducers: {
increment: (state) => {
state.value += 1;
}
}
处理嵌套数据
const todosSlice = createSlice({
name: 'todos',
initialState: { todos: [] },
reducers: {
updateTodo: (state, action) => {
const { id, text } = action.payload;
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.text = text;
}
},
addComment: (state, action) => {
const { todoId, comment } = action.payload;
const todo = state.todos.find(t => t.id === todoId);
if (todo) {
todo.comments.push(comment);
}
}
}
});
面试中的表达
面试中聊到 Redux Toolkit,通常是在考察你对状态管理的理解深度:
Redux Toolkit 的核心改进是简化了 Redux 的 boilerplate。在传统 Redux 中,你需要写 action types、action creators、reducers、store 配置,每一步都很多模板代码。RTK 通过 createSlice 把这些合并成一个配置对象,reducers 里的代码看起来像直接修改 state,但 Immer 会自动处理不可变性。
createAsyncThunk 是处理异步逻辑的标准方案。它接收一个 async 函数,自动生成 pending/fulfilled/rejected 三种 action type,你只需要在 extraReducers 里处理它们就行。
RTK Query 是专门为 React 数据获取设计的。它不只是状态管理,还包含缓存、生命周期管理、loading 状态。对于需要从 API 获取数据的场景,RTK Query 可以大大减少代码量。
延展阅读
- Redux Toolkit 官方文档 — RTK 完整文档
- Redux Toolkit: createSlice — createSlice API
- Redux Toolkit: createAsyncThunk — 异步逻辑处理
- Redux Toolkit: RTK Query — RTK Query 入门
- Redux Essentials: Part 1 — Redux 核心概念