Redux
· 阅读需 9 分钟
Redux 是基于 Flux 架构思想 的 JavaScript 状态管理 库,核心解决了复杂应用中“状态共享、状态变更可追溯、跨组件通信”的问题。它并非 React 专属(可与 Vue/原生 JS 配合),但因 React 单向数据流的特性,二者成为黄金搭档。本文将从核心原理、核心概念拆解、进阶用法到生产级最佳实践,带你系统化掌握 Redux。
一、Redux 核心原理
1. 设计哲学
Redux 遵循三大核心原则:
- 单一数据源:整个应用的状态集中存储在一个
store中,而非分散在各个组件; - 状态只读:唯一修改状态的方式是触发
action(纯对象),禁止直接修改state; - 使用纯函数修改状态:通过
reducer(纯函数)接收旧state和action,返回新state,无副作用。
2. 核心工作流
- 组件通过
dispatch触发一个action(描述“做什么”的纯对象); - Store 接收
action,将当前state和action传入reducer; - Reducer 计算并返回新的
state; - Store 更新状态,并通知所有订阅的组件重新渲染。
二、Redux 核心概念拆解
1. Action
action 是描述“状态变更意图”的纯对象,必须包含 type 字段(唯一标识动作类型),可携带额外数据(payload)。
// 标准 Action(推荐使用 payload 命名额外数据)
const addTodo = (text) => ({
type: 'TODO_ADD',
payload: { text, id: Date.now() }
});
// Action Type 常量(避免魔法字符串)
export const TODO_ADD = 'TODO_ADD';
export const TODO_DELETE = 'TODO_DELETE';
2. Reducer
reducer 是纯函数(无副作用、相同输入必返回相同输出),接收 state 和 action,返回新 state。
- 禁止直接修改
state(必须返回新对象/数组); - 初始状态需合理设置;
- 用
switch/if判断action.type处理不同逻辑。
import { TODO_ADD, TODO_DELETE } from './actionTypes';
// 初始状态
const initialState = {
todos: []
};
// 单个 reducer
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case TODO_ADD:
// 返回新对 象,不修改原 state
return {
...state,
todos: [...state.todos, action.payload]
};
case TODO_DELETE:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload.id)
};
default:
// 未匹配的 action 必须返回原 state
return state;
}
};
3. Store
store 是状态的容器,整个应用只有一个 store,通过 createStore(Redux 核心 API)创建,核心方法:
getState():获取当前状态;dispatch(action):触发状态变更;subscribe(listener):订阅状态变更(返回取消订阅函数)。
import { createStore } from 'redux';
import todoReducer from './reducers/todoReducer';
// 创建 store
const store = createStore(todoReducer);
// 订阅状态变更
const unsubscribe = store.subscribe(() => {
console.log('状态更新:', store.getState());
});
// 触发 action
store.dispatch(addTodo('学习 Redux'));
// 取消订阅
unsubscribe();
4. 中间件(Middleware)
Redux 原生仅支持同步 action,中间件用于扩展 dispatch 能力(处理异步、日志、错误捕获等)。常用中间件:
redux-thunk:处理异步 action(支持返回函数);redux-saga:复杂异步逻辑(如防抖、轮询、取消请求);redux-logger:打印 action/state 变更日志。
示例:redux-thunk 处理异步
// 1. 安装依赖
// npm install redux-thunk
// 2. 配置 store(应用中间件)
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
// 3. 异步 action creator
const fetchTodos = () => {
// 返回函数,接收 dispatch 和 getState
return async (dispatch, getState) => {
dispatch({ type: 'TODO_FETCH_START' });
try {
const res = await fetch('/api/todos');
const todos = await res.json();
dispatch({ type: 'TODO_FETCH_SUCCESS', payload: todos });
} catch (err) {
dispatch({ type: 'TODO_FETCH_ERROR', payload: err.message });
}
};
};
// 4. 组件中调用
store.dispatch(fetchTodos());
三、Redux 进阶用法
1. 拆分 Reducer(combineReducers)
复杂应用中,单个 reducer 会变得臃肿,使用 combineReducers 将多个 reducer 合并为根 reducer:
import { combineReducers } from 'redux';
import todoReducer from './todoReducer';
import userReducer from './userReducer';
// 根 reducer
const rootReducer = combineReducers({
todos: todoReducer, // state.todos 由 todoReducer 管理
user: userReducer // state.user 由 userReducer 管理
});
export default rootReducer;
2. 不可变数据处理
Redux 要求 state 不可变,手动解构赋值易出错,推荐使用工具简化:
immer:通过“可变写法”生成不可变数据(Redux 官方推荐);lodash/fp:提供不可变的数组/对象操作方法。
示例:immer 简化 reducer
// npm install immer
import { produce } from 'immer';
const todoReducer = (state = initialState, action) => {
// produce 接收旧 state 和“可变操作函数”,返回新 state
return produce(state, (draft) => {
switch (action.type) {
case TODO_ADD:
draft.todos.push(action.payload); // 直接修改 draft(immer 自动转换为不可变)
break;
case TODO_DELETE:
const index = draft.todos.findIndex(t => t.id === action.payload.id);
draft.todos.splice(index, 1);
break;
}
});
};
3. Redux Toolkit(RTK):官方推荐的简化方案
Redux 原生写法繁琐(手动写 action type、reducer、处理不可变数据),Redux Toolkit(RTK)是官方封装的“开箱即用”工具集,包含:
createSlice:自动生成 action type、action creator、reducer;configureStore:替代createStore,内置中间件(thunk、immer);createAsyncThunk:简化异步 action 处理。
示例:RTK 重构 Todo 应用
// npm install @reduxjs/toolkit react-redux
// 1. 创建 slice
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 异步 action
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos', // action 前缀
async () => {
const res = await fetch('/api/todos');
return res.json();
}
);
const todoSlice = createSlice({
name: 'todos', // slice 名称(作为 action type 前缀)
initialState: {
list: [],
loading: false,
error: null
},
// 同步 reducer
reducers: {
addTodo: (state, action) => {
state.list.push(action.payload); // immer 支持直接修改
},
deleteTodo: (state, action) => {
state.list = state.list.filter(t => t.id !== action.payload.id);
}
},
// 异步 action 处理
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.loading = true;
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.loading = false;
state.list = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
// 自动生成的 action creator
export const { addTodo, deleteTodo } = todoSlice.actions;
// 2. 配置 store
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: {
todos: todoSlice.reducer
}
});
// 3. React 组件中使用(结合 react-redux)
import { useDispatch, useSelector } from 'react-redux';
import { addTodo, fetchTodos } from './todoSlice';
function TodoList() {
const dispatch = useDispatch();
const { list, loading } = useSelector(state => state.todos);
React.useEffect(() => {
dispatch(fetchTodos());
}, [dispatch]);
return (
<div>
<button onClick={() => dispatch(addTodo({ id: Date.now(), text: 'RTK 学习' }))}>
添加 Todo
</button>
{loading ? <div>加载中...</div> : (
<ul>{list.map(t => <li key={t.id}>{t.text}</li>)}</ul>
)}
</div>
);
}
四、Redux 最佳实践
1. 状态设计原则
- 最小化状态:仅存储“共享状态”和“全局状态”,组件私有状态(如表单输入)无需放入 Redux;
- 扁平化结构:避免嵌套过深的 state(如
state.todos.byId+state.todos.ids替代嵌套数组); - 标准化状态:参考 Normalizr 规范,将嵌套数据转为扁平结构(便于查找/更新)。
2. Action 最佳实践
- 使用常量定义 action type:避免拼写错误,便于维护;
- 统一 payload 命名:额外数据统一放在
payload字段(符合 FSA 规范); - 异步 action 分阶段:如
FETCH_START/FETCH_SUCCESS/FETCH_ERROR,便于处理加载/错误状态。
3. Reducer 最佳实践
- 单一职责:一个 reducer 管理一个领域的状态(如 todos、user);
- 避免副作用:reducer 中禁止发请求、修改 DOM、调用 Math.random() 等;
- 使用 RTK 的 createSlice:替代手动写 reducer,减少样板代码;
- 初始状态合理:包含 loading/error 等状态,避免组件中频繁判空。
4. 性能优化
- 避免不必要的重渲染:
- 使用
useSelector的第二个参数(浅比较):useSelector(selector, shallowEqual); - 拆分组件,仅让依赖状态的组件订阅;
- 使用
- 缓存 selector:使用
reselect创建记忆化 selector,避免重复计算;import { createSelector } from '@reduxjs/toolkit';
// 基础 selector
const selectTodos = state => state.todos.list;
// 记忆化 selector:仅当 todos 变化时重新计算
const selectActiveTodos = createSelector(
[selectTodos],
(todos) => todos.filter(t => !t.completed)
); - 批量更新 action:使用
batch减少多次 dispatch 导致的多次渲染;import { batch } from 'react-redux';
batch(() => {
dispatch(addTodo('1'));
dispatch(addTodo('2'));
});
5. 工程化规范
- 目录结构:按功能/领域划分文件(而非按类型),示例:
src/
features/ # 按功能拆分
todos/ # todo 功能模块
todoSlice.js # RTK slice
TodoList.js # 组件
api.js # 接口请求
store/
index.js # 配置 store
App.js - 避免过度使用 Redux:小应用/简单状态无需 Redux,React Context + useReducer 即可满足;
- 日志与调试:使用
redux-logger或 Redux DevTools(RTK 内置支持),便于追踪状态变更; - 测试:
- Reducer:纯函数易测试,只需验证输入输出;
- Action Creator:测试返回的 action 是否符合预期;
- 异步逻辑:使用
redux-mock-store模拟 store,测试 dispatch 行为。
6. 常见坑点规避
- ❌ 直接修改 state:必须返回新对象/数组(或使用 immer);
- ❌ 忽略 default case:reducer 未匹配的 action 必须返回原 state;
- ❌ 滥用全局状态:组件私有状态(如弹窗显隐)无需放入 Redux;
- ❌ 异步逻辑写在 reducer 中:reducer 是纯函数,异步逻辑需放在 action creator(thunk/saga)中;
- ❌ 未取消订阅:在 React 组件中订阅 store 需在卸载时取消(推荐使用 react-redux 的 hooks,自动处理)。
五、Redux 生态与替代方案
1. 常用生态库
react-redux:Redux 与 React 连接的官方库(提供Provider/useSelector/useDispatch);redux-saga:处理复杂异步逻辑(如取消请求、轮询、并发控制);reselect:记忆化 selector,优化性能;normalizr:标准化嵌套数据,简化状态管理。
2. 替代方案(按需选择)
- Zustand:轻量级状态管理,API 简洁,无需 Provider;
- Recoil/Jotai:React 专属,原子化状态管理,适合细粒度状态;
- MobX:响应式状态管理,学习成本低,适合快速开发;
- Context + useReducer:React 内置,适合中小型应用,无需额外依赖。
总结
- Redux 核心是“单一数据源、状态只读、纯函数修改”,通过 action-reducer-store 完成状态流转;
- Redux Toolkit(RTK)是官方推荐写法,大幅简化样板代码,内置 immer/thunk 等能力;
- 最佳实践核心:最小化状态、扁平化结构、按功能拆分代码、避免过度使用 Redux、重视不可变数据和性能优化;
- 无需为了用 Redux 而用 Redux,小应用可选择 Context + useReducer,复杂异步逻辑可结合 redux-saga。
掌握 Redux 的核心思想而非仅记 API,才能在不同场景下灵活选择状态管理方案,写出可维护、高性能的应用。
