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 处理。
