
15、ReactJS - Flux 简介
你有没有遇到过这样的场景:项目越做越大,组件之间共享的数据越来越多,A组件改了一个状态,B组件莫名其妙崩了;调试的时候根本不知道数据是从哪里来的、被谁改了、又流向了哪里。如果你正在被这些问题折磨,那么这篇文章就是为你写的。
Flux 是 Facebook 官方推出的应用架构,它的核心思想只有四个字:单向数据流。别看只有四个字,它彻底改变了前端状态管理的游戏规则,后来的 Redux、Vuex、MobX 等几乎所有主流状态管理方案,骨子里都流着 Flux 的血。
本文将从痛点出发,带你搞懂 Flux 是什么、为什么用、怎么演进过来的、怎么在企业项目中落地,最后附上竞品对比和面试高频题,让小白也能直接上手。
文章目录
- [15、ReactJS - Flux 简介](#15、ReactJS - Flux 简介)
-
- 一、痛点场景:你是否也被状态管理折磨过?
-
- [1.1 真实场景还原](#1.1 真实场景还原)
- [1.2 具体痛点清单](#1.2 具体痛点清单)
- [二、Flux 是什么?](#二、Flux 是什么?)
-
- [2.1 专业解释](#2.1 专业解释)
- [2.2 大白话解释](#2.2 大白话解释)
- [2.3 生活案例](#2.3 生活案例)
- [三、为什么要用 Flux?](#三、为什么要用 Flux?)
-
- [3.1 解决核心问题](#3.1 解决核心问题)
- [3.2 带来的实际收益](#3.2 带来的实际收益)
- [四、Flux 是怎么演进过来的?](#四、Flux 是怎么演进过来的?)
-
- [4.1 演进时间线](#4.1 演进时间线)
- [五、Flux 核心构成:四大角色详解](#五、Flux 核心构成:四大角色详解)
-
- [5.1 Action:发生了什么](#5.1 Action:发生了什么)
- [5.2 Dispatcher:中央调度员](#5.2 Dispatcher:中央调度员)
- [5.3 Store:数据和业务逻辑的守护者](#5.3 Store:数据和业务逻辑的守护者)
- [5.4 View:React 组件](#5.4 View:React 组件)
- [六、怎么用?从零搭建一个完整的 Flux 应用](#六、怎么用?从零搭建一个完整的 Flux 应用)
-
- [6.1 项目初始化](#6.1 项目初始化)
- [6.2 目录结构](#6.2 目录结构)
- [6.3 第一步:创建 Dispatcher](#6.3 第一步:创建 Dispatcher)
- [6.4 第二步:定义 Action 类型常量](#6.4 第二步:定义 Action 类型常量)
- [6.5 第三步:创建 Action Creator](#6.5 第三步:创建 Action Creator)
- [6.6 第四步:创建 Store](#6.6 第四步:创建 Store)
- [6.7 第五步:创建 View 组件](#6.7 第五步:创建 View 组件)
- [6.8 第六步:组装应用](#6.8 第六步:组装应用)
- 七、企业项目实战:电商购物车
-
- [7.1 多 Store 协作](#7.1 多 Store 协作)
- [7.2 异步 Action 处理](#7.2 异步 Action 处理)
- [7.3 与 React Hooks 结合](#7.3 与 React Hooks 结合)
- 八、常用场景与最佳实践
-
- [8.1 适用场景](#8.1 适用场景)
- [8.2 最佳实践](#8.2 最佳实践)
- [九、竞品对比:Flux vs Redux vs MobX vs Vuex](#九、竞品对比:Flux vs Redux vs MobX vs Vuex)
-
- [9.1 怎么选?](#9.1 怎么选?)
- 十、面试官高频面试题
-
- [面试题1:什么是 Flux?它和 MVC 有什么区别?](#面试题1:什么是 Flux?它和 MVC 有什么区别?)
- [面试题2:Flux 的单向数据流具体是怎么流动的?](#面试题2:Flux 的单向数据流具体是怎么流动的?)
- [面试题3:Dispatcher 的作用是什么?为什么要全局唯一?](#面试题3:Dispatcher 的作用是什么?为什么要全局唯一?)
- [面试题4:Flux 和 Redux 有什么区别和联系?](#面试题4:Flux 和 Redux 有什么区别和联系?)
- [面试题5:Flux 中如何处理异步请求?](#面试题5:Flux 中如何处理异步请求?)
- [面试题6:为什么说 Flux 让状态变得可预测?](#面试题6:为什么说 Flux 让状态变得可预测?)
- [面试题7:Store 之间有依赖怎么办?](#面试题7:Store 之间有依赖怎么办?)
- [面试题8:小项目需要用 Flux 吗?](#面试题8:小项目需要用 Flux 吗?)
- 十一、总结
一、痛点场景:你是否也被状态管理折磨过?
1.1 真实场景还原
想象你在开发一个电商网站,有以下几个组件:
- 顶部导航栏:显示购物车商品数量
- 商品列表页:每个商品有"加入购物车"按钮
- 购物车侧边栏:显示商品明细和总价
- 商品详情页:也有"加入购物车"按钮
用户在商品列表页点击"加入购物车",顶部导航的数字要加1,购物车侧边栏要更新,总价要重新计算。如果用户同时在多个标签页操作,数据同步更是一场灾难。
在传统的 MVC 架构中,View 可以直接改 Model,Model 变化又触发 View 更新,多个 View 和多个 Model 之间互相调用,很快就变成了一团乱麻。

1.2 具体痛点清单
| 痛点 | 表现 | 后果 |
|---|---|---|
| 数据流向不明 | 不知道状态被谁改了 | 调试困难,Bug难复现 |
| 级联更新 | 一个状态变化触发连锁反应 | 页面闪烁、性能问题 |
| 状态分散 | 同一个数据存在多个地方 | 数据不一致、显示矛盾 |
| 双向绑定混乱 | View改Model,Model改View | 循环更新、死循环 |
| 新人上手难 | 老项目逻辑盘根错节 | 维护成本高,不敢改代码 |
Facebook 当年在开发消息通知功能时就踩了这个大坑:多个独立的UI模块都需要访问"未读消息数",用 Backbone 风格的 MVC 代码根本理不清逻辑,未读数经常显示错误,用户明明读了消息还显示有未读,这就是著名的"未读消息Bug"。
二、Flux 是什么?
2.1 专业解释
Flux 是 Facebook 于 2014 年 F8 开发者大会上正式发布的一种前端应用架构模式。它不是一个框架,也不是一个库,而是一种利用单向数据流来组合 React 视图组件的架构思想。Flux 官方的参考实现只有区区一百多行代码,核心就是一个 Dispatcher。
Flux 应用由四个核心部分构成:
- Action(动作):视图层发出的消息,描述发生了什么事,是一个普通的 JavaScript 对象
- Dispatcher(分发器):全局唯一的中央调度器,接收所有 Action 并分发给注册的 Store
- Store(数据层):存放应用状态和业务逻辑,状态变化后发出 change 事件
- View(视图层):React 组件,监听 Store 的 change 事件并重新渲染

2.2 大白话解释
说人话,Flux 就像一家餐厅的运作流程:
- 顾客(View):坐在座位上,不能直接去厨房炒菜
- 点餐(Action):顾客喊"我要一份宫保鸡丁",这就是一个 Action
- 服务员(Dispatcher):唯一的传菜员,把订单送到厨房,顾客不能自己送
- 厨房(Store):收到订单后做菜,菜做好了喊一声"上菜了"
- 顾客(View):听到"上菜了",就看到菜端上来了
整个过程中,顾客永远不能直接进厨房炒菜,厨房也不会主动把菜塞给顾客,一切都通过服务员(Dispatcher)中转,这就是单向流动。
2.3 生活案例
再举一个更贴近生活的例子:银行转账。
你(View)想给朋友转钱,你不能直接去银行金库(Store)里拿钱给朋友。你必须:
- 填写转账单(Action),写明转多少钱、转给谁
- 把单子交给柜台(Dispatcher)
- 柜台通知金库(Store)处理转账
- 金库处理完,更新账户余额,发出"交易完成"通知
- 你(View)收到通知,看到余额变化了
如果任何人都能直接去金库拿钱,那银行就乱套了。Flux 就是给前端应用建立了这样一套"银行级"的状态管理纪律。
三、为什么要用 Flux?
3.1 解决核心问题
Flux 解决的根本问题是状态可预测性。在大型应用中,状态变化的来源可能有几十上百个,如果每个组件都能直接修改状态,那么出了问题根本无从查起。
Flux 通过强制单向数据流,保证了:
- 数据流向唯一:永远是 Action -> Dispatcher -> Store -> View,不会反过来
- 状态修改收敛:只有 Store 能改自己的状态,View 只能发 Action,不能直接改
- 变更可追溯:每个状态变化都对应一个 Action,调试时可以一步步回放
- 组件解耦:View 不需要知道数据怎么改的,只需要发 Action 和监听变化
3.2 带来的实际收益
| 收益 | 说明 |
|---|---|
| 调试效率提升 | 每个状态变化都有 Action 记录,可追溯 |
| 团队协作顺畅 | 职责清晰,前端工程师各司其职 |
| 代码可维护性 | 新功能不会轻易引入老模块的 Bug |
| 测试更容易 | Store 是纯逻辑,可独立单元测试 |
| 性能可控 | 避免了双向绑定导致的级联更新和重复渲染 |
四、Flux 是怎么演进过来的?
4.1 演进时间线

第一阶段:MVC 时代(2014年以前)
早期前端应用普遍采用 MVC 模式。Model 负责数据,View 负责展示,Controller 负责逻辑。但在前端场景下,MVC 很快变了味:View 和 Model 之间经常直接双向通信,Controller 变得臃肿不堪,多个 Model 和多个 View 交叉依赖,形成了"意大利面式"的代码。
Facebook 的工程师在维护庞大的前端代码库时痛苦不堪,每次加新功能都容易引入新 Bug,因为模块之间的依赖关系让系统变得"脆弱而且不可预测"。
第二阶段:Flux 诞生(2014年)
2014年5月,Facebook 在官方博客发布了《Flux: An Application Architecture for React》,正式提出 Flux 架构。同年 F8 大会上,Jing Chen 做了主题演讲,详细介绍了 Flux 的设计思想。
Flux 的核心创新就是单向数据流。它抛弃了 MVC 中 Controller 的中心化设计,改用 Dispatcher 做纯技术分发,把业务逻辑下放到 Store 中。Flux 不是框架,而是一种模式,官方参考实现只有一个 Dispatcher 类。
第三阶段:Redux 崛起(2015年)
2015年,Dan Abramov 在准备 React Europe 演讲时,为了演示"时间旅行调试"而创建了 Redux。Redux 继承了 Flux 的单向数据流思想,但做了三个关键改进:
- 单一 Store(Flux 是多个 Store)
- 纯函数 Reducer(Flux 的 Store 是类,有副作用)
- 不可变数据(Flux 的 Store 可以直接改内部状态)
Redux 迅速成为 React 生态的事实标准,几乎成了"状态管理"的代名词。
第四阶段:百花齐放(2016年至今)
- MobX:走响应式路线,通过 observable 自动追踪依赖,代码量少,学习曲线平缓
- Vuex:Vue 官方状态管理,融合了 Flux 和 Redux 的思想,专为 Vue 优化
- Zustand:极简主义,API 友好,包体小
- Recoil:Facebook 官方出品,面向 React 并发特性
- Redux Toolkit:Redux 官方工具包,解决样板代码过多的问题
虽然工具层出不穷,但它们的底层思想几乎都能追溯到 Flux。理解了 Flux,就理解了所有状态管理方案的根。
五、Flux 核心构成:四大角色详解

5.1 Action:发生了什么
Action 是一个普通的 JavaScript 对象,用来描述"发生了什么事"。它必须有一个 type 字段表示动作类型,还可以携带额外的数据(payload)。
javascript
// 一个典型的 Action 对象
{
type: 'ADD_TO_CART',
payload: {
productId: 123,
quantity: 2
}
}
通常我们会用 Action Creator 函数来统一创建 Action:
javascript
// actions/CartActions.js
export const addToCart = (productId, quantity) => {
return {
type: 'ADD_TO_CART',
payload: { productId, quantity }
};
};
export const removeFromCart = (productId) => {
return {
type: 'REMOVE_FROM_CART',
payload: { productId }
};
};
Action 是纯描述,不包含任何逻辑,它只负责说"发生了什么",不负责"怎么处理"。
5.2 Dispatcher:中央调度员
Dispatcher 是 Flux 中唯一的全局单例,它的职责非常纯粹:接收 Action,分发给所有注册的 Store。Dispatcher 不包含任何业务逻辑,它就是一个"分发器"。
核心机制:
- Store 通过
dispatcher.register(callback)注册回调 - View 通过
dispatcher.dispatch(action)发送 Action - Dispatcher 收到 Action 后,依次调用所有注册的回调
- 支持
waitFor()方法,处理 Store 之间的依赖顺序
javascript
// dispatcher/AppDispatcher.js
import { Dispatcher } from 'flux';
const AppDispatcher = new Dispatcher();
export default AppDispatcher;
就是这么简单。Flux 官方的 Dispatcher 实现只有一百多行代码,但它是整个架构的枢纽。
5.3 Store:数据和业务逻辑的守护者
Store 存放应用的状态和业务逻辑。一个 Flux 应用可以有多个 Store,每个 Store 管理自己领域的状态(比如 CartStore、UserStore、ProductStore)。
Store 的特点:
- 继承 EventEmitter,状态变化后 emit 'change' 事件
- 注册到 Dispatcher,根据 Action 的 type 决定如何更新状态
- 对外只暴露 getter 方法,不暴露 setter,外部不能直接改状态
- 状态修改只能在 Store 内部,通过 Dispatcher 分发的 Action 触发
javascript
// stores/CartStore.js
import { EventEmitter } from 'events';
import AppDispatcher from '../dispatcher/AppDispatcher';
const CHANGE_EVENT = 'change';
// 内部状态,不对外暴露
let _cart = [];
// 内部方法,只能在 Store 内部调用
function _addItem(product) {
const existing = _cart.find(item => item.id === product.id);
if (existing) {
existing.quantity += 1;
} else {
_cart.push({ ...product, quantity: 1 });
}
}
function _removeItem(productId) {
_cart = _cart.filter(item => item.id !== productId);
}
const CartStore = Object.assign({}, EventEmitter.prototype, {
// 对外暴露的 getter
getCart() {
return _cart;
},
getTotalPrice() {
return _cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
// 发射变化事件
emitChange() {
this.emit(CHANGE_EVENT);
},
// 允许 View 订阅变化
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
// 注册到 Dispatcher
CartStore.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case 'ADD_TO_CART':
_addItem(action.payload.product);
CartStore.emitChange();
break;
case 'REMOVE_FROM_CART':
_removeItem(action.payload.productId);
CartStore.emitChange();
break;
default:
// 不处理的 Action 什么都不做
break;
}
});
export default CartStore;
5.4 View:React 组件
View 就是 React 组件,它有两个职责:
- 从 Store 读取状态,渲染页面
- 响应用户交互,通过 Action Creator 发送 Action 到 Dispatcher
View 永远不能直接修改 Store 的状态,它只能"读"和"发 Action"。
javascript
// components/CartView.jsx
import React, { useState, useEffect } from 'react';
import CartStore from '../stores/CartStore';
import { addToCart, removeFromCart } from '../actions/CartActions';
const CartView = () => {
const [cart, setCart] = useState(CartStore.getCart());
useEffect(() => {
// 订阅 Store 变化
const onChange = () => setCart(CartStore.getCart());
CartStore.addChangeListener(onChange);
return () => CartStore.removeChangeListener(onChange);
}, []);
const handleAdd = (product) => {
// 发送 Action,不直接改状态
addToCart(product);
};
const handleRemove = (productId) => {
removeFromCart(productId);
};
return (
<div className="cart">
<h2>购物车</h2>
<ul>
{cart.map(item => (
<li key={item.id}>
{item.name} - {item.quantity}件 - ¥{item.price * item.quantity}
<button onClick={() => handleRemove(item.id)}>删除</button>
</li>
))}
</ul>
<p>总价:¥{CartStore.getTotalPrice()}</p>
</div>
);
};
export default CartView;
注意上面的代码:addToCart 和 removeFromCart 只是创建了 Action 对象,还需要 dispatch 到 Dispatcher。在实际项目中,Action Creator 通常会直接调用 Dispatcher:
javascript
// actions/CartActions.js
import AppDispatcher from '../dispatcher/AppDispatcher';
export const addToCart = (product) => {
AppDispatcher.dispatch({
type: 'ADD_TO_CART',
payload: { product }
});
};
export const removeFromCart = (productId) => {
AppDispatcher.dispatch({
type: 'REMOVE_FROM_CART',
payload: { productId }
});
};
这样 View 调用 Action Creator 就等于发送了 Action,代码更简洁。
六、怎么用?从零搭建一个完整的 Flux 应用
下面我们用一个 TodoList 应用,从零搭建一个完整的 Flux 项目,让你直接上手。
6.1 项目初始化
bash
# 创建项目
npx create-react-app flux-todo-demo
cd flux-todo-demo
# 安装 flux
npm install flux
6.2 目录结构
text
src/
├── dispatcher/
│ └── AppDispatcher.js # 全局唯一的 Dispatcher
├── actions/
│ ├── TodoActions.js # Todo 相关的 Action Creator
│ └── ActionTypes.js # Action 类型常量
├── stores/
│ └── TodoStore.js # Todo 状态管理
├── components/
│ ├── TodoInput.jsx # 输入框组件
│ ├── TodoList.jsx # 列表组件
│ └── TodoItem.jsx # 单个 Todo 项
├── App.jsx
└── index.js
6.3 第一步:创建 Dispatcher
javascript
// src/dispatcher/AppDispatcher.js
import { Dispatcher } from 'flux';
// 全局唯一的 Dispatcher 实例
const AppDispatcher = new Dispatcher();
export default AppDispatcher;
6.4 第二步:定义 Action 类型常量
javascript
// src/actions/ActionTypes.js
// 用常量管理 Action 类型,避免拼写错误
export const TODO_CREATE = 'TODO_CREATE';
export const TODO_COMPLETE = 'TODO_COMPLETE';
export const TODO_UNDO_COMPLETE = 'TODO_UNDO_COMPLETE';
export const TODO_UPDATE_TEXT = 'TODO_UPDATE_TEXT';
export const TODO_DESTROY = 'TODO_DESTROY';
export const TODO_DESTROY_COMPLETED = 'TODO_DESTROY_COMPLETED';
export const TODO_TOGGLE_COMPLETE_ALL = 'TODO_TOGGLE_COMPLETE_ALL';
6.5 第三步:创建 Action Creator
javascript
// src/actions/TodoActions.js
import AppDispatcher from '../dispatcher/AppDispatcher';
import * as ActionTypes from './ActionTypes';
export const TodoActions = {
create(text) {
AppDispatcher.dispatch({
type: ActionTypes.TODO_CREATE,
text: text.trim()
});
},
updateText(id, text) {
AppDispatcher.dispatch({
type: ActionTypes.TODO_UPDATE_TEXT,
id,
text: text.trim()
});
},
toggleComplete(todo) {
const actionType = todo.complete
? ActionTypes.TODO_UNDO_COMPLETE
: ActionTypes.TODO_COMPLETE;
AppDispatcher.dispatch({
type: actionType,
id: todo.id
});
},
toggleCompleteAll() {
AppDispatcher.dispatch({
type: ActionTypes.TODO_TOGGLE_COMPLETE_ALL
});
},
destroy(id) {
AppDispatcher.dispatch({
type: ActionTypes.TODO_DESTROY,
id
});
},
destroyCompleted() {
AppDispatcher.dispatch({
type: ActionTypes.TODO_DESTROY_COMPLETED
});
}
};
6.6 第四步:创建 Store
javascript
// src/stores/TodoStore.js
import { EventEmitter } from 'events';
import AppDispatcher from '../dispatcher/AppDispatcher';
import * as ActionTypes from '../actions/ActionTypes';
const CHANGE_EVENT = 'change';
// 内部数据,外部无法直接访问
let _todos = {};
// 内部工具方法
function create(text) {
const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
_todos[id] = {
id,
complete: false,
text
};
}
function update(id, updates) {
_todos[id] = Object.assign({}, _todos[id], updates);
}
function updateAll(updates) {
for (const id in _todos) {
update(id, updates);
}
}
function destroy(id) {
delete _todos[id];
}
function destroyCompleted() {
for (const id in _todos) {
if (_todos[id].complete) {
destroy(id);
}
}
}
const TodoStore = Object.assign({}, EventEmitter.prototype, {
// 判断是否全部完成
areAllComplete() {
for (const id in _todos) {
if (!_todos[id].complete) {
return false;
}
}
return true;
},
// 获取所有 Todo
getAll() {
return _todos;
},
// 发射变化事件
emitChange() {
this.emit(CHANGE_EVENT);
},
// 订阅变化
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
// 取消订阅
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
// 注册到 Dispatcher,处理所有 Action
TodoStore.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case ActionTypes.TODO_CREATE:
if (action.text !== '') {
create(action.text);
TodoStore.emitChange();
}
break;
case ActionTypes.TODO_TOGGLE_COMPLETE_ALL:
if (TodoStore.areAllComplete()) {
updateAll({ complete: false });
} else {
updateAll({ complete: true });
}
TodoStore.emitChange();
break;
case ActionTypes.TODO_UNDO_COMPLETE:
update(action.id, { complete: false });
TodoStore.emitChange();
break;
case ActionTypes.TODO_COMPLETE:
update(action.id, { complete: true });
TodoStore.emitChange();
break;
case ActionTypes.TODO_UPDATE_TEXT:
if (action.text !== '') {
update(action.id, { text: action.text });
TodoStore.emitChange();
}
break;
case ActionTypes.TODO_DESTROY:
destroy(action.id);
TodoStore.emitChange();
break;
case ActionTypes.TODO_DESTROY_COMPLETED:
destroyCompleted();
TodoStore.emitChange();
break;
default:
// 不处理的 Action 忽略
break;
}
});
export default TodoStore;
6.7 第五步:创建 View 组件
javascript
// src/components/TodoInput.jsx
import React, { useState } from 'react';
import { TodoActions } from '../actions/TodoActions';
const TodoInput = () => {
const [value, setValue] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (value.trim()) {
TodoActions.create(value);
setValue('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="需要做什么?"
/>
<button type="submit">添加</button>
</form>
);
};
export default TodoInput;
javascript
// src/components/TodoItem.jsx
import React from 'react';
import { TodoActions } from '../actions/TodoActions';
const TodoItem = ({ todo }) => {
return (
<li style={{ textDecoration: todo.complete ? 'line-through' : 'none' }}>
<input
type="checkbox"
checked={todo.complete}
onChange={() => TodoActions.toggleComplete(todo)}
/>
<span>{todo.text}</span>
<button onClick={() => TodoActions.destroy(todo.id)}>删除</button>
</li>
);
};
export default TodoItem;
javascript
// src/components/TodoList.jsx
import React, { useState, useEffect } from 'react';
import TodoStore from '../stores/TodoStore';
import TodoItem from './TodoItem';
import { TodoActions } from '../actions/TodoActions';
const TodoList = () => {
const [todos, setTodos] = useState(TodoStore.getAll());
useEffect(() => {
const onChange = () => {
setTodos({ ...TodoStore.getAll() });
};
TodoStore.addChangeListener(onChange);
return () => TodoStore.removeChangeListener(onChange);
}, []);
const todoArray = Object.values(todos);
return (
<div>
<label>
<input
type="checkbox"
checked={TodoStore.areAllComplete()}
onChange={TodoActions.toggleCompleteAll}
/>
全选
</label>
<ul>
{todoArray.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
{todoArray.some(t => t.complete) && (
<button onClick={TodoActions.destroyCompleted}>清除已完成</button>
)}
</div>
);
};
export default TodoList;
6.8 第六步:组装应用
javascript
// src/App.jsx
import React from 'react';
import TodoInput from './components/TodoInput';
import TodoList from './components/TodoList';
function App() {
return (
<div className="App" style={{ maxWidth: '500px', margin: '50px auto' }}>
<h1>Todo List - Flux 架构演示</h1>
<TodoInput />
<TodoList />
</div>
);
}
export default App;
bash
# 启动项目
npm start
打开浏览器,你就拥有了一个完整的 Flux 架构 Todo 应用。整个数据流清晰可见:用户输入 -> Action -> Dispatcher -> Store -> View 更新。
七、企业项目实战:电商购物车

在企业项目中,Flux 通常不会单独使用,而是会结合异步请求、多 Store 协作、Store 间依赖等场景。下面以电商购物车为例,展示企业级的 Flux 实践。
7.1 多 Store 协作
企业应用通常有多个 Store:ProductStore 管理商品数据,CartStore 管理购物车,UserStore 管理用户信息。Store 之间可能存在依赖,比如添加购物车时需要先确认用户是否登录。
Flux 通过 Dispatcher 的 waitFor 方法处理 Store 间依赖:
javascript
// stores/CartStore.js
import AppDispatcher from '../dispatcher/AppDispatcher';
import UserStore from './UserStore';
// ...
CartStore.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case 'ADD_TO_CART':
// 等待 UserStore 处理完这个 Action
AppDispatcher.waitFor([UserStore.dispatchToken]);
if (UserStore.isLoggedIn()) {
_addItem(action.payload.product);
CartStore.emitChange();
} else {
// 未登录,提示登录
AppDispatcher.dispatch({ type: 'SHOW_LOGIN_MODAL' });
}
break;
// ...
}
});
7.2 异步 Action 处理
企业项目中大量操作是异步的(API 请求)。Flux 本身不处理异步,通常的做法是在 Action Creator 中发起异步请求,请求完成后再 dispatch 一个新的 Action:
javascript
// actions/CartActions.js
import AppDispatcher from '../dispatcher/AppDispatcher';
import CartAPI from '../api/CartAPI';
export const CartActions = {
// 异步获取购物车数据
fetchCart() {
// 先 dispatch 一个"开始加载"的 Action
AppDispatcher.dispatch({ type: 'CART_FETCH_START' });
CartAPI.getCart()
.then(cart => {
AppDispatcher.dispatch({
type: 'CART_FETCH_SUCCESS',
payload: { cart }
});
})
.catch(error => {
AppDispatcher.dispatch({
type: 'CART_FETCH_ERROR',
payload: { error: error.message }
});
});
},
// 异步添加商品到购物车
addToCart(productId, quantity) {
AppDispatcher.dispatch({ type: 'CART_ADD_START' });
CartAPI.addItem(productId, quantity)
.then(updatedCart => {
AppDispatcher.dispatch({
type: 'CART_ADD_SUCCESS',
payload: { cart: updatedCart }
});
})
.catch(error => {
AppDispatcher.dispatch({
type: 'CART_ADD_ERROR',
payload: { error: error.message }
});
});
}
};
javascript
// stores/CartStore.js
let _cart = [];
let _loading = false;
let _error = null;
CartStore.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case 'CART_FETCH_START':
case 'CART_ADD_START':
_loading = true;
_error = null;
CartStore.emitChange();
break;
case 'CART_FETCH_SUCCESS':
case 'CART_ADD_SUCCESS':
_cart = action.payload.cart;
_loading = false;
CartStore.emitChange();
break;
case 'CART_FETCH_ERROR':
case 'CART_ADD_ERROR':
_loading = false;
_error = action.payload.error;
CartStore.emitChange();
break;
}
});
这种"请求中 -> 成功/失败"的三态 Action 模式,是企业项目中处理异步的标准做法。
7.3 与 React Hooks 结合
在现代 React 项目中,可以自定义 Hook 来简化 Store 订阅逻辑:
javascript
// hooks/useStore.js
import { useState, useEffect } from 'react';
export function useStore(store, selector) {
const [state, setState] = useState(() => selector(store));
useEffect(() => {
const onChange = () => setState(selector(store));
store.addChangeListener(onChange);
return () => store.removeChangeListener(onChange);
}, [store, selector]);
return state;
}
javascript
// components/CartBadge.jsx
import React from 'react';
import CartStore from '../stores/CartStore';
import { useStore } from '../hooks/useStore';
const CartBadge = () => {
const itemCount = useStore(CartStore, store => store.getItemCount());
return (
<div className="cart-badge">
购物车 ({itemCount})
</div>
);
};
export default CartBadge;
八、常用场景与最佳实践
8.1 适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 大型企业应用 | 强烈推荐 | 状态复杂,需要可预测性和可维护性 |
| 多模块共享状态 | 推荐 | 多个组件需要读写同一份数据 |
| 团队协作项目 | 推荐 | 统一架构规范,降低沟通成本 |
| 需要时间旅行调试 | 推荐(用Redux) | Action 记录可回放 |
| 小型工具类应用 | 不推荐 | 杀鸡用牛刀,增加不必要的复杂度 |
| 纯展示型页面 | 不推荐 | 本地 state 足够 |
8.2 最佳实践
1. Action 类型用常量管理
不要在代码里写字符串字面量,用常量统一管理,避免拼写错误导致的隐蔽 Bug。
2. 一个 Action 只描述一件事
Action 应该是细粒度的,不要一个 Action 干多件事。比如不要用 UPDATE_USER_AND_CART,而是拆成 UPDATE_USER 和 UPDATE_CART 两个 Action。
3. Store 对外只暴露 getter
Store 的状态只能通过 Action 修改,外部不能直接 set。对外只提供 get 方法,保证数据安全。
4. 避免在 View 中写业务逻辑
业务逻辑应该放在 Store 中,View 只负责渲染和发 Action。这样 Store 可以独立测试。
5. 异步操作放在 Action Creator
不要在 Store 中发异步请求,Store 应该是纯同步的。异步请求放在 Action Creator 中,完成后 dispatch 结果 Action。
6. 合理拆分 Store
按业务领域拆分 Store,不要一个全局 Store 管所有东西。但也不要拆得太细,一般按领域模块划分即可。
九、竞品对比:Flux vs Redux vs MobX vs Vuex

虽然 Flux 是鼻祖,但在实际项目中,大家更多使用的是它的"后代"。下面是四种主流方案的全面对比:
| 对比维度 | Flux | Redux | MobX | Vuex |
|---|---|---|---|---|
| 核心思想 | 单向数据流,多Store | 单一Store,纯函数Reducer | 响应式,自动追踪依赖 | 单向数据流,专为Vue设计 |
| Store数量 | 多个Store | 单一Store | 多个observable | 单一Store(可模块化) |
| 状态可变性 | 可变(Store内部直接改) | 不可变(返回新对象) | 可变(直接修改) | 可变(mutation中修改) |
| 修改方式 | Dispatcher分发Action | dispatch Action -> Reducer | 直接修改observable | commit mutation |
| 异步处理 | Action Creator中处理 | 中间件(redux-thunk/saga) | autorunAsync / action | actions(支持异步) |
| 调试能力 | 一般(无时间旅行) | 极强(时间旅行、DevTools) | 一般 | 较强(Vue DevTools) |
| 样板代码 | 中等 | 多(Action/Reducer/常量) | 少 | 中等 |
| 学习曲线 | 平缓 | 陡峭(函数式编程概念) | 平缓 | 平缓(Vue官方) |
| 适用框架 | React(不限) | React(不限) | React/Vue/Angular | 仅Vue |
| 包体积 | 小 | 中等 | 中等 | 小 |
| 类型支持 | 一般 | 好(TS友好) | 好 | 好 |
| 企业级成熟度 | 高(Facebook验证) | 极高(生态最完善) | 高 | 高(Vue官方) |
| 当前维护状态 | 官方已归档 | 活跃(Redux Toolkit主推) | 活跃 | 活跃(Pinia为新一代) |
9.1 怎么选?
- 如果你在用 React 做大型企业项目:选 Redux Toolkit,它是 Redux 的官方推荐用法,解决了样板代码问题
- 如果你追求极简、讨厌样板代码:选 Zustand 或 MobX,API 简洁,上手快
- 如果你在用 Vue:选 Pinia(Vuex 的继任者),Vue 官方推荐
- 如果你想理解状态管理的本质:学 Flux,它是所有方案的思想根源
- 如果项目很小、状态不多:用 React 自带的 useState/useContext/useReducer 就够了,不需要引入状态管理库
十、面试官高频面试题
面试题1:什么是 Flux?它和 MVC 有什么区别?
参考答案:
Flux 是 Facebook 提出的一种前端应用架构模式,核心是单向数据流。它由 Action、Dispatcher、Store、View 四个部分组成,数据永远沿着 Action -> Dispatcher -> Store -> View 的方向流动,不会反向流动。
和 MVC 的核心区别:
- MVC 中 View 和 Model 之间可以双向通信,容易导致级联更新和循环依赖;Flux 严格单向,View 不能直接改 Model
- MVC 的 Controller 是中心化的,容易变得臃肿;Flux 的 Dispatcher 只做分发,不含业务逻辑,业务逻辑分散在各个 Store 中
- MVC 中数据流向不明确,调试困难;Flux 中每个状态变化都对应一个 Action,可追溯
面试题2:Flux 的单向数据流具体是怎么流动的?
参考答案:
- 用户与 View 交互,View 调用 Action Creator 创建一个 Action
- Action 被 dispatch 到全局唯一的 Dispatcher
- Dispatcher 将 Action 分发给所有注册的 Store
- Store 根据 Action 的 type 决定是否更新自己的状态
- Store 状态更新后,emit 'change' 事件
- View 监听到 'change' 事件,从 Store 重新获取数据并重新渲染
整个过程中数据只往一个方向走,任何相邻部分之间都不会双向流动。
面试题3:Dispatcher 的作用是什么?为什么要全局唯一?
参考答案:
Dispatcher 是 Flux 的中央调度器,负责接收所有 Action 并分发给注册的 Store。它是一个纯技术组件,不包含任何业务逻辑。
全局唯一的原因:
- 保证所有 Action 都经过同一个入口,便于追踪和调试
- 支持 Store 之间的依赖管理,通过
waitFor方法可以控制 Store 处理 Action 的顺序 - 如果有多个 Dispatcher,Action 的分发路径就会变得复杂,破坏单向数据流的清晰性
面试题4:Flux 和 Redux 有什么区别和联系?
参考答案:
联系:Redux 是在 Flux 思想基础上发展而来的,都遵循单向数据流,都用 Action 描述状态变化。
区别:
- Flux 有多个 Store,Redux 只有一个单一 Store
- Flux 的 Store 是可变的,可以直接修改内部状态;Redux 的状态不可变,Reducer 必须返回新对象
- Flux 有 Dispatcher 的概念;Redux 没有独立的 Dispatcher,store.dispatch 内置了分发逻辑
- Flux 的 Store 是类,继承 EventEmitter;Redux 用纯函数 Reducer 处理状态
- Redux 支持中间件机制,Flux 没有
- Redux 有强大的 DevTools 和时间旅行调试能力,Flux 原生不支持
面试题5:Flux 中如何处理异步请求?
参考答案:
Flux 本身不处理异步,标准做法是在 Action Creator 中发起异步请求:
- 先 dispatch 一个"请求开始"的 Action(用于显示 loading 状态)
- 发起异步请求(API 调用)
- 请求成功后 dispatch 一个"成功"Action,携带返回数据
- 请求失败后 dispatch 一个"失败"Action,携带错误信息
- Store 根据不同的 Action 更新对应状态
不要在 Store 中发异步请求,Store 应该保持纯同步逻辑,这样便于测试和预测。
面试题6:为什么说 Flux 让状态变得可预测?
参考答案:
Flux 通过三个机制保证可预测性:
- 单一修改入口:状态只能通过 Action -> Dispatcher -> Store 这条路径修改,View 不能直接改状态,杜绝了"谁都能改"的混乱
- 单向数据流:数据流向固定,不会出现循环更新和级联更新,出了问题可以沿着数据流一步步排查
- Action 即日志:每个状态变化都对应一个 Action 对象,记录了"什么时候、发生了什么、带了什么数据",相当于天然的操作日志
面试题7:Store 之间有依赖怎么办?
参考答案:
Flux 通过 Dispatcher 的 waitFor 方法处理 Store 间依赖。当 Store A 的处理逻辑依赖 Store B 的处理结果时,Store A 可以在回调中调用 AppDispatcher.waitFor([StoreB.dispatchToken]),等待 Store B 处理完当前 Action 后再继续自己的逻辑。
这保证了在同一个 Action 的处理过程中,Store 之间有明确的执行顺序,避免竞态条件。
面试题8:小项目需要用 Flux 吗?
参考答案:
不一定。Flux 的价值在于管理复杂的共享状态,如果项目很小、组件之间状态交互少,用 React 自带的 useState/useContext 就足够了。引入 Flux 会增加 Action、Dispatcher、Store 等样板代码,反而降低开发效率。
一般判断标准:当你发现状态在多个不相关组件之间共享、且修改来源超过3个时,再考虑引入状态管理方案。
十一、总结
Flux 虽然已经不是当下最热门的状态管理工具,但它的思想是整个前端状态管理领域的基石。理解了 Flux 的单向数据流,你就能快速上手 Redux、Vuex、MobX、Zustand 等任何方案,因为它们本质上都是 Flux 思想的不同实现。
学习 Flux 不是为了在项目中直接用它,而是为了理解状态管理的本质:状态修改必须收敛、数据流向必须清晰、变更过程必须可追溯。这三条原则,无论你用什么框架、什么库,都是通用的。
希望这篇文章能帮你彻底搞懂 Flux。如果觉得有帮助,欢迎点赞收藏。
本文为原创文章,如需转载,请联系作者获得授权,并注明出处。