03-状态管理与路由——09. Redux Toolkit 与 React-Redux
·
09. Redux Toolkit 与 React-Redux
一、5W1H 概述
| 维度 | 内容 |
|---|---|
| What | 使用 Redux Toolkit 简化配置,React-Redux 连接 React |
| Why | 减少样板代码,提供更好的开发体验 |
| When | 使用 Redux 的项目 |
| Where | store 配置和组件中 |
| Who | 选择 Redux 的开发者 |
| How | configureStore、createSlice、useSelector、useDispatch |
二、What - 什么是 Redux Toolkit?
Redux Toolkit 是 Redux 官方推荐的工具集,简化了 Redux 的使用。
安装
npm install @reduxjs/toolkit react-redux
为什么用 Redux Toolkit?
| 特性 | 传统 Redux | Redux Toolkit |
|---|---|---|
| 配置 | 复杂 | 简单 |
| 样板代码 | 多 | 少 |
| 不可变更新 | 手动 | 自动(Immer) |
| 异步支持 | 需要配置 | 内置 |
三、Why - 为什么需要 Redux Toolkit?
3.1 减少样板代码
传统 Redux 需要大量样板代码,Redux Toolkit 大幅简化。
3.2 内置最佳实践
自动配置 Redux DevTools、Thunk 中间件。
3.3 Immer 集成
可以直接编写"可变"代码,Immer 自动处理不可变性。
四、When - 何时使用?
| 场景 | 推荐程度 | 说明 |
|---|---|---|
| 新 Redux 项目 | ✅ 强烈推荐 | 官方推荐 |
| 现有 Redux 项目迁移 | ✅ 推荐 | 逐步迁移 |
| 大型复杂应用 | ✅ 推荐 | 更好的开发体验 |
| 简单状态管理 | ❌ 不推荐 | 使用 Zustand |
五、Where - 在哪里使用?
src/
├── store/
│ ├── index.js # Store 配置
│ ├── slices/
│ │ ├── counterSlice.js
│ │ ├── userSlice.js
│ │ └── todoSlice.js
│ └── hooks.js # 自定义 hooks
└── components/
└── Component.jsx # 使用 Redux
六、Who - 谁需要使用?
使用 Redux 进行状态管理的开发者。
七、How - 如何使用?
7.1 创建 Slice
// store/slices/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // 可以直接修改!Immer 处理
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
reset: (state) => {
state.value = 0;
}
}
});
// 导出 actions
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
// 导出 reducer
export default counterSlice.reducer;
7.2 配置 Store
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slices/counterSlice';
import userReducer from './slices/userSlice';
import todoReducer from './slices/todoSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
user: userReducer,
todos: todoReducer
}
});
// 导出 RootState 和 AppDispatch 类型(TypeScript)
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
7.3 Provider 配置
// App.jsx
import { Provider } from 'react-redux';
import { store } from './store';
import Counter from './components/Counter';
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
7.4 组件中使用
// components/Counter.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset } from '../store/slices/counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>计数: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(reset())}>重置</button>
</div>
);
}
7.5 自定义 Hooks(TypeScript)
// store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './index';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// 组件中使用
import { useAppDispatch, useAppSelector } from '../store/hooks';
function Counter() {
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
// ...
}
7.6 异步操作(createAsyncThunk)
// store/slices/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 异步 thunk
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId, { rejectWithValue }) => {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
const data = await response.json();
return data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const updateUser = createAsyncThunk(
'user/updateUser',
async ({ id, data }) => {
const response = await fetch(`/api/users/${id}`, {
method: 'PUT',
body: JSON.stringify(data)
});
return response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null
},
reducers: {
clearUser: (state) => {
state.data = null;
state.error = null;
}
},
extraReducers: (builder) => {
builder
// fetchUser
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
})
// updateUser
.addCase(updateUser.fulfilled, (state, action) => {
state.data = action.payload;
});
}
});
export const { clearUser } = userSlice.actions;
export default userSlice.reducer;
7.7 完整 Todo 示例
// store/slices/todoSlice.js
import { createSlice } from '@reduxjs/toolkit';
const todoSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
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;
}
},
deleteTodo: (state, action) => {
return state.filter(todo => todo.id !== action.payload);
},
editTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload.id);
if (todo) {
todo.text = action.payload.text;
}
},
clearCompleted: (state) => {
return state.filter(todo => !todo.completed);
}
}
});
export const { addTodo, toggleTodo, deleteTodo, editTodo, clearCompleted } = todoSlice.actions;
export default todoSlice.reducer;
// components/TodoApp.jsx
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo, deleteTodo, clearCompleted } from '../store/slices/todoSlice';
function TodoApp() {
const [text, setText] = useState('');
const todos = useSelector((state) => state.todos);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text));
setText('');
}
};
const activeCount = todos.filter(t => !t.completed).length;
return (
<div>
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">添加</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => dispatch(deleteTodo(todo.id))}>删除</button>
</li>
))}
</ul>
<div>
<p>剩余 {activeCount} 项</p>
{activeCount !== todos.length && (
<button onClick={() => dispatch(clearCompleted())}>清除已完成</button>
)}
</div>
</div>
);
}
7.8 选择器优化
// store/selectors.js
// 创建可复用的选择器
export const selectCount = (state) => state.counter.value;
export const selectUser = (state) => state.user.data;
export const selectTodos = (state) => state.todos;
export const selectActiveTodos = (state) => state.todos.filter(t => !t.completed);
export const selectCompletedTodos = (state) => state.todos.filter(t => t.completed);
export const selectTodoById = (id) => (state) => state.todos.find(t => t.id === id);
// 组件中使用
import { selectActiveTodos } from '../store/selectors';
function ActiveTodoList() {
const activeTodos = useSelector(selectActiveTodos);
// ...
}
八、传统 Redux vs Redux Toolkit 对比
| 对比项 | 传统 Redux | Redux Toolkit |
|---|---|---|
| Store 创建 | createStore |
configureStore |
| Reducer | 手动编写 | createSlice |
| 不可变更新 | 手动展开 | 自动(Immer) |
| Action Types | 常量定义 | 自动生成 |
| Action Creators | 手动编写 | 自动生成 |
| 异步支持 | 需要配置中间件 | 内置 Thunk |
| 代码量 | 多 | 少 70% |
九、练习题
- 使用 Redux Toolkit 创建计数器应用
- 使用 Redux Toolkit 创建 Todo 应用
- 使用 createAsyncThunk 实现用户数据获取
十、小结
| 要点 | 说明 |
|---|---|
| createSlice | 同时创建 reducer 和 actions |
| configureStore | 自动配置中间件和 DevTools |
| createAsyncThunk | 简化异步操作 |
| useSelector | 读取状态 |
| useDispatch | 触发 action |
更多推荐

所有评论(0)