全局状态管理:Flux架构与Redux
从一个真实的痛点说起
你有没有遇到过这种场景?一个数据在十几个组件间传来传去,props 层层嵌套,改一处逻辑得翻遍整个组件树。我之前接手过一个后台项目,十几个表单共享一份用户配置,每次修改都得顺着 props 链往上找源头,调试到崩溃。
说白了,这就是典型的 props drilling 问题。组件间通信一旦复杂起来,传统的事件总线或者父子传参根本扛不住。
核心矛盾:组件树越深,跨模块通信的链路就越脆弱。改一个叶子组件的数据,可能要波及整棵树的中间节点。
Flux 架构:单向数据流的启蒙
Facebook 当年在开发 Messenger 时也踩过同样的坑。他们的解决方案就是 Flux 架构。我第一次看到 Flux 的流程图时,第一反应是:「就这?绕这么大一圈?」后来真用上了才明白——单向循环才是最稳的。
Flux 的四层结构
- View:只管渲染和触发用户操作,不存业务状态。
- Action:描述「发生了什么」,是一个普通对象,带 type 和 payload。
- Dispatcher:中枢调度器,负责把 Action 派发给所有注册的 Store。
- Store:持有状态和业务逻辑,View 直接订阅它。
数据只能沿着 View → Action → Dispatcher → Store → View 这个方向转圈。你想想看,这样是不是每个环节都是可预测的?出 bug 也能顺着链条往回查。
我个人习惯把 Flux 的调度机制类比成「广播电台」—— Dispatcher 就是发射塔,所有 Store 都是收音机。同一套信号,谁收谁处理,互不干扰。
Redux:Flux 的最佳实践与进化
Flux 虽然思路清晰,但落地时有个麻烦:每个项目都得自己写 Dispatcher 和 Store 的模板代码。我不止一次在团队里抱怨过:「能不能有个现成的轮子?」Redux 就是那个答案。
Redux 在 Flux 基础上做了三个关键简化:
- 单一 Store:整个应用只有一棵状态树,调试和序列化都方便。
- Reducer 纯函数:状态更新逻辑全部收敛到纯函数里,输入输出可预测。
- 中间件机制:副作用(异步请求、日志)通过中间件链处理,不污染核心流程。
我曾经踩过的坑:Reducer 里不要直接修改 state。有一次我图省事写了 state.items.push(newItem),结果时间旅行调试直接废了。血的教训——始终返回新对象。
一个最小化的 Redux 示例
// Action
const addTodo = (text) => ({
type: 'ADD_TODO',
payload: { text, id: Date.now() }
});
// Reducer
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, { ...action.payload, completed: false }];
default:
return state;
}
};
// Store
import { createStore } from 'redux';
const store = createStore(todos);
store.subscribe(() => console.log(store.getState()));
store.dispatch(addTodo('学习 Flux 架构'));
注意看,Reducer 里用了扩展运算符 [...state, ...],而不是 push。这就是不可变数据的写法。我每次 code review 都会盯着这一行看。
中间件:Redux 的「插件系统」
Flux 没解决异步问题,Redux 用中间件优雅地补上了。中间件本质上是对 dispatch 的层层包装。你发一个 Action,它先过日志中间件,再过异步中间件,最后才到 Reducer。
我个人最常用的是 redux-thunk,简单直接:
const fetchUser = (id) => async (dispatch) => {
dispatch({ type: 'FETCH_USER_START' });
try {
const user = await api.getUser(id);
dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (err) {
dispatch({ type: 'FETCH_USER_ERROR', payload: err.message });
}
};
中间件的执行顺序就像洋葱圈——从外到内,再从内到外。这也是 Redux 最让我喜欢的设计之一。
性能优化:别让全局状态拖垮渲染
全局状态管理虽然方便,但用不好就是性能灾难。我见过一个项目,所有数据全塞在 Store 里,每次 dispatch 都要重新计算整个视图——卡得不行。
这里几个实用的优化手段:
| 手段 | 说明 | 我遇到的典型场景 |
|---|---|---|
| reselect 选择器 | 带缓存的计算属性,只有依赖变化时才重新计算 | 列表过滤 + 排序,避免每次渲染都重算 |
| 浅比较绑定 | connect 或 useSelector 使用浅比较,减少无效更新 | 大型表单,只更新变化字段对应的组件 |
| 拆分 Store | 用 combineReducers 按模块拆分,粒度更细 | 用户模块、订单模块独立管理 |
| 不可变数据 | 配合 Immer 或手动不可变操作,保证引用比较准确 | 深层嵌套的数据结构更新 |
reselect 的缓存机制说白了就是「懒计算」——没人读取就不算,数据没变就复用上次结果。我一般用它来包装所有派生数据,性价比极高。
Flux vs Redux 对比一览
| 维度 | Flux | Redux |
|---|---|---|
| Store 数量 | 多个 | 单一 |
| 状态更新方式 | Store 自包含逻辑 | 纯函数 Reducer |
| 调度器 | 独立 Dispatcher | 内置在 Store 中 |
| 副作用处理 | 无标准方案 | 中间件机制 |
| 调试体验 | 需手动实现 | 时间旅行、DevTools 开箱即用 |
我个人看法:Flux 适合理解设计思想,Redux 适合直接上手干活。如果团队规模不大、业务复杂度中等,直接用 Redux 更省心。
本章知识体系总览
下面这张图把 Flux 和 Redux 的核心流程串在一起了。你看一眼就能明白数据是怎么转圈的。
到底选 Flux 还是 Redux?
我个人建议:
- 小项目或概念验证:用手写 Flux 理解原理就够了,别上 Redux 全家桶。
- 中大型业务系统:直接上 Redux + Toolkit。模板代码少,中间件生态成熟。
- 已有旧项目用 Flux:不必强迁。如果状态逻辑不复杂,Flux 也能跑得很好。
记住一句话:状态管理是手段,不是目的。别为了用 Redux 而用 Redux。我见过不少项目,明明用 React Context 就能搞定,硬上 Redux 反而增加了复杂度。
本章小结
- Flux 用单向数据流解决了跨模块通信的混乱问题。
- Redux 在 Flux 基础上做了减法——单一 Store、纯函数 Reducer、中间件链。
- 性能优化的三个关键词:选择器缓存、浅比较、不可变数据。
- 中间件是 Redux 的精华,合理使用可以让副作用逻辑清晰可控。
嗯,这一章的内容就到这儿。单向数据流的思路其实不只在 Redux 里用,你写组件、设计接口、甚至规划业务流程时都能借鉴。核心就是——让数据朝着一个方向走,别绕圈子。