Redux Toolkit #
一、Redux Toolkit概述 #
1.1 什么是Redux Toolkit #
Redux Toolkit(RTK)是 Redux 官方推荐的编写 Redux 逻辑的方式,简化了 Redux 的使用。
1.2 为什么使用RTK #
| 问题 | RTK解决方案 |
|---|---|
| 配置复杂 | 简单的configureStore |
| 样板代码多 | createSlice自动生成 |
| 异步处理麻烦 | 内置createAsyncThunk |
| 不可变更新 | 内置Immer库 |
1.3 安装 #
bash
npm install @reduxjs/toolkit react-redux
二、configureStore #
2.1 基本配置 #
javascript
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './features/todosSlice';
import usersReducer from './features/usersSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
users: usersReducer
}
});
// 导出类型(TypeScript)
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
2.2 配置选项 #
javascript
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(logger)
.concat(customMiddleware),
devTools: process.env.NODE_ENV !== 'production',
preloadedState: {
todos: [{ id: 1, text: 'Initial todo' }]
}
});
三、createSlice #
3.1 基本用法 #
javascript
import { createSlice } from '@reduxjs/toolkit';
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
// 可以直接修改state(Immer处理)
state.push({
id: Date.now(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
removeTodo: (state, action) => {
return state.filter(t => t.id !== action.payload);
}
}
});
// 导出actions
export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
// 导出reducer
export default todosSlice.reducer;
3.2 准备Action Payload #
javascript
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: {
reducer: (state, action) => {
state.push(action.payload);
},
prepare: (text) => {
return {
payload: {
id: Date.now(),
text,
completed: false,
createdAt: new Date().toISOString()
}
};
}
}
}
});
3.3 复杂状态 #
javascript
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
filter: 'all',
loading: false,
error: null
},
reducers: {
setFilter: (state, action) => {
state.filter = action.payload;
},
clearCompleted: (state) => {
state.items = state.items.filter(t => !t.completed);
}
}
});
四、createAsyncThunk #
4.1 基本用法 #
javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 创建异步thunk
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async (_, { rejectWithValue }) => {
try {
const response = await fetch('/api/todos');
if (!response.ok) {
throw new Error('Failed to fetch');
}
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const addTodoAsync = createAsyncThunk(
'todos/addTodoAsync',
async (text, { rejectWithValue }) => {
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
loading: false,
error: null
},
reducers: {},
extraReducers: (builder) => {
builder
// fetchTodos
.addCase(fetchTodos.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
})
// addTodoAsync
.addCase(addTodoAsync.fulfilled, (state, action) => {
state.items.push(action.payload);
});
}
});
4.2 带参数的异步请求 #
javascript
export const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId, { getState, rejectWithValue }) => {
const { auth } = getState();
try {
const response = await fetch(`/api/users/${userId}`, {
headers: {
Authorization: `Bearer ${auth.token}`
}
});
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
五、在组件中使用 #
5.1 配置Provider #
javascript
import { Provider } from 'react-redux';
import { store } from './store';
function App() {
return (
<Provider store={store}>
<TodoApp />
</Provider>
);
}
5.2 使用Hooks #
javascript
import { useSelector, useDispatch } from 'react-redux';
import {
addTodo,
toggleTodo,
removeTodo,
fetchTodos,
selectFilteredTodos
} from './features/todosSlice';
function TodoApp() {
const dispatch = useDispatch();
const todos = useSelector(selectFilteredTodos);
const { loading, error } = useSelector(state => state.todos);
const [input, setInput] = useState('');
useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
const handleAdd = () => {
if (input.trim()) {
dispatch(addTodo(input));
setInput('');
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
/>
<button onClick={handleAdd}>添加</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
onClick={() => dispatch(toggleTodo(todo.id))}
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</span>
<button onClick={() => dispatch(removeTodo(todo.id))}>
删除
</button>
</li>
))}
</ul>
</div>
);
}
5.3 自定义Hooks #
javascript
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// 类型化的hooks
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// 使用
function Component() {
const dispatch = useAppDispatch();
const todos = useAppSelector(state => state.todos);
}
六、最佳实践 #
6.1 文件结构 #
text
src/
├── store/
│ └── index.js
├── features/
│ ├── todos/
│ │ ├── todosSlice.js
│ │ └── TodosList.jsx
│ └── users/
│ ├── usersSlice.js
│ └── UsersList.jsx
└── hooks/
└── redux.js
6.2 选择器模式 #
javascript
// todosSlice.js
export const selectAllTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;
export const selectFilteredTodos = createSelector(
[selectAllTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
}
);
export const selectTodoById = (state, id) =>
state.todos.items.find(t => t.id === id);
6.3 持久化 #
javascript
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistConfig = {
key: 'root',
storage,
whitelist: ['todos']
};
const rootReducer = combineReducers({
todos: todosReducer,
users: usersReducer
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE']
}
})
});
export const persistor = persistStore(store);
七、完整示例 #
javascript
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer
}
});
// features/todos/todosSlice.js
import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async () => {
const response = await fetch('/api/todos');
return response.json();
}
);
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
filter: 'all',
loading: false,
error: null
},
reducers: {
addTodo: (state, action) => {
state.items.push({
id: Date.now(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.items.find(t => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
},
removeTodo: (state, action) => {
state.items = state.items.filter(t => t.id !== action.payload);
},
setFilter: (state, action) => {
state.filter = action.payload;
}
},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.loading = true;
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
});
}
});
export const { addTodo, toggleTodo, removeTodo, setFilter } = todosSlice.actions;
export const selectTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;
export const selectFilteredTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
if (filter === 'active') return todos.filter(t => !t.completed);
if (filter === 'completed') return todos.filter(t => t.completed);
return todos;
}
);
export default todosSlice.reducer;
八、总结 #
| 要点 | 说明 |
|---|---|
| configureStore | 简化store配置 |
| createSlice | 自动生成actions和reducer |
| createAsyncThunk | 处理异步操作 |
| Immer | 自动处理不可变更新 |
核心原则:
- 使用 RTK 简化 Redux 开发
- 直接修改 state(Immer 处理)
- 使用 createAsyncThunk 处理异步
- 使用选择器封装状态访问
最后更新:2026-03-26