Appearance
Redux
本文介绍Redux状态管理库,包括核心概念(Store、Action、Reducer)、工作流程以及在React应用中的集成方法。
什么是Redux
Redux是JavaScript应用的可预测状态容器,它提供了一种可靠的方式来管理应用的状态,特别适合于中大型单页应用(SPA)。Redux遵循三个基本原则:
- 单一数据源:整个应用的状态存储在单个store的对象树中
- 状态只读:唯一改变状态的方法是触发action
- 使用纯函数修改:通过reducer(纯函数)来指定状态树的变化
核心概念
Store
Store是保存应用状态的对象,它提供了以下API:
getState()- 获取当前状态dispatch(action)- 触发状态更新subscribe(listener)- 注册监听器replaceReducer(nextReducer)- 替换reducer(高级用法)
一个应用只有一个store,它包含了应用的所有状态。
javascript
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);Action
Action是描述发生了什么的普通JavaScript对象,它必须有一个type属性来表示动作的类型。
javascript
// 简单的action对象
{
type: 'ADD_TODO',
text: '学习Redux'
}
// 使用action creator函数创建action
function addTodo(text) {
return {
type: 'ADD_TODO',
text
};
}Reducer
Reducer是纯函数,接收当前状态和一个action,返回新的状态。
javascript
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: Date.now(),
text: action.text,
completed: false
}
];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id
? { ...todo, completed: !todo.completed }
: todo
);
default:
return state;
}
}组合Reducers
对于复杂应用,可以将reducer拆分为多个小的reducer,每个负责状态树的一部分,然后使用combineReducers组合它们。
javascript
import { combineReducers } from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';
const rootReducer = combineReducers({
todos,
visibilityFilter
});
export default rootReducer;Redux工作流程
- 用户与UI交互,触发事件
- 事件处理函数dispatch一个action
- Redux store调用reducer函数
- Reducer计算新状态并返回
- Store更新状态,并通知所有订阅者
- 订阅了store的UI组件获取新状态并重新渲染
┌─────────────────────────────────────────┐
│ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ Actions │──────▶│ Reducers │ │
│ └─────────┘ └──────────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ UI │◀──────│ Store │ │
│ └─────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────┘在React中使用Redux
安装依赖
bash
npm install redux react-redux
# 或
yarn add redux react-redux创建Store
javascript
// src/store/index.js
import { createStore } from 'redux';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
// 可选:使用Redux DevTools扩展
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;提供Store
使用Provider组件将store提供给整个应用:
jsx
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);连接组件
使用connect高阶组件或useSelector/useDispatch Hooks连接组件与Redux:
使用connect(类组件)
jsx
import React from 'react';
import { connect } from 'react-redux';
import { addTodo, toggleTodo } from '../actions';
class TodoList extends React.Component {
render() {
const { todos, addTodo, toggleTodo } = this.props;
return (
<div>
<button onClick={() => addTodo('新任务')}>添加任务</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
</div>
);
}
}
const mapStateToProps = state => ({
todos: state.todos
});
const mapDispatchToProps = {
addTodo,
toggleTodo
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);使用Hooks(函数组件)
jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo } from '../actions';
function TodoList() {
const todos = useSelector(state => state.todos);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(addTodo('新任务'))}>
添加任务
</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => dispatch(toggleTodo(todo.id))}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))}
</ul>
</div>
);
}
export default TodoList;Redux中间件
中间件提供了一个第三方扩展点,位于dispatch action和到达reducer之间。中间件可以用来记录日志、报告错误、与异步API通信、路由等。
常用中间件
- redux-thunk: 处理异步action
- redux-saga: 更复杂的异步流程管理
- redux-logger: 记录action和状态变化
- redux-persist: 持久化状态
配置中间件
javascript
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducers';
const middlewares = [thunk];
// 在开发环境添加logger
if (process.env.NODE_ENV === 'development') {
middlewares.push(logger);
}
// 启用Redux DevTools
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(...middlewares))
);
export default store;使用redux-thunk处理异步操作
javascript
// actions.js
import axios from 'axios';
// 同步action creators
export const fetchTodosRequest = () => ({
type: 'FETCH_TODOS_REQUEST'
});
export const fetchTodosSuccess = todos => ({
type: 'FETCH_TODOS_SUCCESS',
payload: todos
});
export const fetchTodosFailure = error => ({
type: 'FETCH_TODOS_FAILURE',
payload: error
});
// 异步action creator (使用thunk)
export const fetchTodos = () => {
return async dispatch => {
dispatch(fetchTodosRequest());
try {
const response = await axios.get('https://api.example.com/todos');
dispatch(fetchTodosSuccess(response.data));
} catch (error) {
dispatch(fetchTodosFailure(error.message));
}
};
};Redux工具包(Redux Toolkit)
Redux Toolkit是Redux官方推荐的工具集,用于简化Redux开发,减少样板代码。
安装
bash
npm install @reduxjs/toolkit
# 或
yarn add @reduxjs/toolkit使用createSlice
javascript
import { createSlice } from '@reduxjs/toolkit';
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
// Redux Toolkit允许在reducers中直接"修改"状态
// 实际上它使用了Immer库来确保不可变更新
state.push({
id: Date.now(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.find(todo => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
removeTodo: (state, action) => {
return state.filter(todo => todo.id !== action.payload);
}
}
});
// 导出action creators
export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
// 导出reducer
export default todosSlice.reducer;配置Store
javascript
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';
import filtersReducer from './filtersSlice';
const store = configureStore({
reducer: {
todos: todosReducer,
filters: filtersReducer
}
});
export default store;使用createAsyncThunk处理异步操作
javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
// 创建异步thunk
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async (_, { rejectWithValue }) => {
try {
const response = await axios.get('https://api.example.com/todos');
return response.data;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null
},
reducers: {
// 其他reducers...
},
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload;
});
}
});
export default todosSlice.reducer;Redux最佳实践
目录结构
推荐使用"特性文件夹"或"Duck模式"组织Redux代码:
src/
features/
todos/
todosSlice.js // 包含reducer和actions
TodoList.js // 组件
TodoItem.js // 组件
filters/
filtersSlice.js // 包含reducer和actions
FilterPanel.js // 组件
app/
store.js // Redux store配置
App.js
index.js性能优化
- 避免不必要的渲染:使用
React.memo、useCallback和useMemo
jsx
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { toggleTodo } from './todosSlice';
const TodoItem = React.memo(({ id, text, completed }) => {
const dispatch = useDispatch();
// 使用useCallback缓存回调函数
const handleToggle = useCallback(() => {
dispatch(toggleTodo(id));
}, [dispatch, id]);
return (
<li
onClick={handleToggle}
style={{ textDecoration: completed ? 'line-through' : 'none' }}
>
{text}
</li>
);
});
export default TodoItem;- 选择性地订阅状态:只订阅组件需要的状态部分
jsx
// 不好的做法:订阅整个状态
const todos = useSelector(state => state);
// 好的做法:只订阅需要的部分
const todos = useSelector(state => state.todos);
// 更好的做法:使用选择器函数
const selectCompletedTodos = state => state.todos.filter(todo => todo.completed);
const completedTodos = useSelector(selectCompletedTodos);- 使用Reselect库进行记忆化选择
javascript
import { createSelector } from '@reduxjs/toolkit';
const selectTodos = state => state.todos;
const selectFilter = state => state.filter;
export const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case 'all':
return todos;
case 'completed':
return todos.filter(todo => todo.completed);
case 'active':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
);常见陷阱
- 修改状态:Redux要求reducer是纯函数,不应直接修改状态
javascript
// 错误:直接修改状态
function badReducer(state, action) {
state.completed = true; // 不要这样做!
return state;
}
// 正确:返回新状态
function goodReducer(state, action) {
return {
...state,
completed: true
};
}- 过度使用Redux:不是所有状态都需要放在Redux中
jsx
// 本地组件状态使用useState
function Counter() {
// 这种简单的UI状态不需要放在Redux中
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}- 过度抽象:保持简单,只在需要时添加复杂性
Redux vs 其他状态管理方案
| 特性 | Redux | Context API | MobX | Zustand |
|---|---|---|---|---|
| 学习曲线 | 陡峭 | 平缓 | 中等 | 平缓 |
| 样板代码 | 多 | 少 | 少 | 少 |
| 可预测性 | 高 | 中 | 中 | 高 |
| 性能 | 良好 | 有限 | 良好 | 良好 |
| 开发工具 | 强大 | 基本 | 良好 | 良好 |
| 中间件 | 丰富 | 无 | 有限 | 有 |
| 社区支持 | 强大 | 良好 | 良好 | 增长中 |
| 适用规模 | 中-大 | 小-中 | 中-大 | 小-中-大 |
总结
Redux是一个强大的状态管理库,特别适合中大型应用。它提供了可预测的状态管理,丰富的中间件生态系统和强大的开发工具。
主要优点:
- 可预测的单向数据流
- 集中式状态管理
- 强大的开发工具和调试能力
- 丰富的中间件生态系统
- 大型社区支持
主要缺点:
- 学习曲线陡峭
- 需要编写大量样板代码(可通过Redux Toolkit缓解)
- 对于小型应用可能过于复杂
随着Redux Toolkit的推出,Redux变得更加易用,减少了样板代码,并提供了更好的开发体验。对于需要复杂状态管理的应用,Redux仍然是一个强大的选择。