跳到主要内容

Redux

· 阅读需 9 分钟
ahKevinXy
作者

Redux 是基于 Flux 架构思想 的 JavaScript 状态管理库,核心解决了复杂应用中“状态共享、状态变更可追溯、跨组件通信”的问题。它并非 React 专属(可与 Vue/原生 JS 配合),但因 React 单向数据流的特性,二者成为黄金搭档。本文将从核心原理、核心概念拆解、进阶用法到生产级最佳实践,带你系统化掌握 Redux。

一、Redux 核心原理

1. 设计哲学

Redux 遵循三大核心原则:

  • 单一数据源:整个应用的状态集中存储在一个 store 中,而非分散在各个组件;
  • 状态只读:唯一修改状态的方式是触发 action(纯对象),禁止直接修改 state
  • 使用纯函数修改状态:通过 reducer(纯函数)接收旧 stateaction,返回新 state,无副作用。

2. 核心工作流

  1. 组件通过 dispatch 触发一个 action(描述“做什么”的纯对象);
  2. Store 接收 action,将当前 stateaction 传入 reducer
  3. Reducer 计算并返回新的 state
  4. 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纯函数(无副作用、相同输入必返回相同输出),接收 stateaction,返回新 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 内置,适合中小型应用,无需额外依赖。

总结

  1. Redux 核心是“单一数据源、状态只读、纯函数修改”,通过 action-reducer-store 完成状态流转;
  2. Redux Toolkit(RTK)是官方推荐写法,大幅简化样板代码,内置 immer/thunk 等能力;
  3. 最佳实践核心:最小化状态、扁平化结构、按功能拆分代码、避免过度使用 Redux、重视不可变数据和性能优化;
  4. 无需为了用 Redux 而用 Redux,小应用可选择 Context + useReducer,复杂异步逻辑可结合 redux-saga。

掌握 Redux 的核心思想而非仅记 API,才能在不同场景下灵活选择状态管理方案,写出可维护、高性能的应用。