前言: 本来想学习总结下Redux、Mobx, 可是说到这两个, 那就不得不提一下 Flux, 他们都是使用单向数据流来集中管理应用的状态变化, 以及触发页面的数据更新. 所以这篇文章先简单介绍一下Flux.
一、介绍
Flux 的出现背景和具体细节不做详细介绍, 感兴趣的可以参考官网. Flux中有四个角色, 分别是Action、Dispatcher、Store、View
1、Dispatcher
作为 Flux 的中心, 负责管理数据流的工作, 所有的 Store 将会共用一个 Dispatcher. 可以认为 Dispathcer 管理着一张注册表 callback list, 每当定义一个 Store 都会向注册表里添加上自己的回调函数 func, 当 Action Creator 触发一个 Action, Dispatcher 会按注册表的顺序逐个执行callback list中所有的函数, 回调函数会根据实际情况去选择是否要更新 state 状态.
2、Stores
负责统一管理 Flux 中的状态和逻辑, 这里可以认为是特定域的状态, 他包括了逻辑域的单例模型 FluxStore 和 逻辑域的模型集合 FluxStoreGroup. 每个 Store 通常定义时会向 Dispatcher 注册一个回调函数, 这个回调函数会接收一个 action , 然后会根据 action 的类型检查是否需要执行或执行哪一种状态更新操作, 等待所有 Store 状态检查更新完后会广播一个 change 事件, 通知 Views 进行自动更新.
3、Views
基于React的视图层逻辑, 我们可以根据 state 的变化去更新视图, 例如通过 setState 方法触发 React 视图的更新. 而在 Flux 中, Views 负责监听其所依赖 Store 的广播事件, 它从 Store 中获取到变化的 state, 并控制页面的更新. 同时 Views 也能够接收到视图层事件的触发, 通过 Actions 去改变 Store 的状态.
4、Actions
其利用 Dispatcher 暴露的一个方法 dispatch, 该方法可以传入一些有效的参数去触发 Store 的状态改变, 而不同的 action 就是针对与不同参数的 dispatch 方法的封装调用. 我们可以将不同的 action 绑定到视图层的不同事件中, 通过视图事件的触发, 从而调用 dispatch 去更改 Store.
Actions 通常包括具体的 action 和 action creator, action 是一个对操作的方法描述, 可以理解为一个 payload 对象
代码语言:javascript复制{
type: TodoActionTypes.ADD_TODO, // action 的类型, 用于 disatcher 触发时的执行筛选
text: '', // 需要更新的 state 值
}
action creator 是和 action 相关的事件函数, 为了便于统一的管理, 会把所有的 creator 放在一起
代码语言:javascript复制const Actions = {
// 单个creator, 执行 dispatch 事件
addTodo(text) {
TodoDispatcher.dispatch({
type: TodoActionTypes.ADD_TODO,
text,
});
},
}
二、深入源码
Dsipatcher
既然是中心枢纽, 那我们先来看一下Dispatcher的使用和实现. 在使用上很简单, 直接实例化即可, 实例化的对象会用在 Actions 和 Stores 中.
代码语言:javascript复制import { Dispatcher } from 'flux';
export default new Dispatcher();
其实现如下:
关键内容说明
1、 _callbacks
以 key-value 的形式维护一个回调函数的注册表, FluxStore 和 FluxStoreGroup 在实例化的时候会调用register 函数向注册表里添加各自的 callback, register 通过自增的 _lastID 计算出唯一的 DispatchToken, 作为 Store 的注册标识, 该标识可以用于 disptach.waitFor 中调用, 后面详细说明
代码语言:javascript复制register(callback: (payload: TPayload) => void): DispatchToken {
var id = _prefix this._lastID ; // 唯一标识id
this._callbacks[id] = callback; // 向注册表中添加回调函数
return id; // 返回标识
}
2、 dispatch
当某个 action 想要更新 Store 时, 会通过 dispatch 来进行, dispatch 会逐一执行注册表中的所有回调函数, 各回调函数会根据 payload 来决定是否需要更新自己的 state. 大致流程如下:
a) 在执行前会先清理当前的执行状态 (startDispatching), 将注册表中所有回调的当前执行状态 (_isPending) 清除、缓存当前的 payload (_pendingPayload)、将正在执行dispatch的状态标识置位 (_isDispatching)
b) 按注册表顺序执行回调函数, 并将回调函数的执行状态置位 (_isPending), 避免重复执行
c) 恢复执行状态 (_stopDispatching), 将缓存payload清掉 (pendingPayload), 并将执行状态清除 (_isDispatching)
代码语言:javascript复制dispatch(payload: TPayload): void {
this._startDispatching(payload); // 清理状态
try {
for (var id in this._callbacks) {
if (this._isPending[id]) { // 执行标识位置位
continue;
}
this._invokeCallback(id); // 执行注册表中回调函数
}
} finally {
this._stopDispatching(); // 恢复状态
}
}
3、 waitFor
可以用来管理各 Stores 的一些依赖, 比如等待其他 Stores 的回调函数执行完后, 再执行当前 Store 的回调. 他也用于 FluxStoreGroup 中, 等待所有状态更新后, 再去触发 React 的 state 变化, 这个后面介绍 FluxContainer 时再详细说明
代码语言:javascript复制// 传入DispatchToken数组, DispatchToken在register时生成
waitFor(ids: Array<DispatchToken>): void {
for (var ii = 0; ii < ids.length; ii ) {
var id = ids[ii];
if (this._isPending[id]) { // 等待ids中的回调执行完毕
continue;
}
this._invokeCallback(id); // 执行当前的回调函数
}
}
Stores
源码中分为FluxStore、FluxReduceStore, 具体调用链关系如下
用户定义的 Store 类继承于 FluxReduceStore, 而 FluxReduceStore 继承于 FluxStore. FluxStore 作为抽象类使用, 不直接用于继承和实现, FluxReduceStore 也不直接用于实例化, 所有的实例化都需要调用方自定义继承于 FluxReduceStore 的 Store 类.
关键内容说明
1、dispatcher
Store 实例化时必须要传入 dispathcer (由 Dsipatcher 实现) 用于向注册表中注册回调函数
代码语言:javascript复制class FluxStore {
...
constructor(dispatcher: Dispatcher<any>): void {
this.__changeEvent = 'change'; // 定义状态变更事件名
this.__dispatcher = dispatcher; // 初始化dispatcher
this.__emitter = new EventEmitter(); // 初始化事件发生器
this._dispatchToken = dispatcher.register((payload) => {
this.__invokeOnDispatch(payload);
}); // 向dispatch注册表中注册回调函数
}
}
2、 reduce
用于改变当前状态的函数, 一般是 switch...case 的实现, 根据传入的 state 和 action 来判断是否需要更新当前的状态
代码语言:javascript复制// state: 当前状态, action: Action触发的payload
reduce(state: number, action: Object): number {
switch(action.type) { // 根据触发的类型, 选择性的更新state
case: 'add':
return state action.value;
case: 'double':
return state * 2;
default:
return state;
}
}
3、 addListener
允许 Store 实例监听 change 状态改变事件, 并对监听回调函数进行处理. 一般会在 React 组件中对此事件进行订阅
代码语言:javascript复制addListener(callback: (eventType?: string) => void): {remove: () => void} {
// 当状态改变时, 会触发chagne事件
return this.__emitter.addListener(this.__changeEvent, callback);
}
4、 __invokeOnDispatch
该方法用于监听 Action 的事件触发, 并且根据用户定义的 reduce 去更新状态, 当有状态变更后会广播一个 change 事件通知 Views.
代码语言:javascript复制__invokeOnDispatch(action: Object): void {
this.__changed = false; // 清除更新状态
const startingState = this._state; // 缓存当前state
// 获取更新后的state
const endingState = this.reduce(startingState, action);
// 如果更新前后state的值不一样
if (!this.areEqual(startingState, endingState)) {
this._state = endingState; // 将当前state更新到最新值
this.__emitChange(); // 将this.__changed置为true
}
if (this.__changed) {
// 触发'change'事件, 通知组件有状态更新
this.__emitter.emit(this.__changeEvent);
}
}
Views
这一层是通过 FluxContainer、FluxContainerSubscriptions 来实现对 React Components的封装, 将 React 组件与相应的 Stores 关联起来, 并订阅监听 Stores 的变化, 对变化做出响应, 从而更新视图
1、 FluxContainer
其包含两个函数 create 和 createFunctional, 他们的唯一区别就是分别用来 React 的处理类组件和函数式组件, createFunctional 会把函数式组件转换成类组件, 然后再使用 create 进行处理. 所以我们重点看下 create 函数.
代码语言:javascript复制function create<DefaultProps, Props, State>(
Base: ReactClass<Props>, // React类组件
options?: ?Options, // 可选项
): ReactClass<Props>
a) Base: 是 React 的类组件, Flux 要求组件传入的组件内必须实现 getStores (获取 Stores 列表)、calculateState (获取 Store 中的状态) 两个方法
代码语言:javascript复制class FooContainer extends Component {
static getStores(props) {
return [FooStore];
}
static calculateState(prevState, props) {
return {
foo: FooStore.getState(),
};
}
render() {
return <FooView {...this.state} />;
}
}
module.exports = FluxContainer.create(FooContainer);
b) options: 有三个可选项, 分别是 pure、withProps (作用于getStores、calculateState)、withContext (作用于getStores、calculateState)
代码语言:javascript复制const DEFAULT_OPTIONS = {
pure: true, // 是否是纯组件
withProps: false, // 是否依赖于当前的props
withContext: false, // 是否依赖于上下文
};
2、 FluxContainerSubscriptions
每一个被 Flux 封装的组件内部都会有一个 FluxContainerSubscriptions 实例, 该实例是用来订阅 Stores 的变化的. 让我们来看一下关键代码:
代码语言:javascript复制class FluxContainerSubscriptions {
...
setStores(stores: Array<FluxStore>): void {
...
// 创建Store的change事件监听函数, 并逐个注入Store中, 当有一个Store状态发证变化, 则属性changed置为true
const setChanged = () => { changed = true; };
this._tokens = stores.map((store) => store.addListener(setChanged));
...
// 向FluxStoreGroup中注册回调函数, 当所有的Dispatcher注册表中所有的函数执行完后, 触发此回调
const callCallbacks = () => {
if (changed) {
this._callbacks.forEach((fn) => fn());
changed = false;
}
};
this._storeGroup = new FluxStoreGroup(stores, callCallbacks);
}
// 添加监听函数, 订阅Stores状态的变化
addListener(fn: () => void): void {
this._callbacks.push(fn);
}
...
}
3、 ContainerClass
Flux 最终导出的组件, 组件通过订阅 Stores 的状态变化, 将最新的 state 状态通过 React 的 setState 触发视图更新
代码语言:javascript复制class ContainerClass extends Base {
...
constructor(props: Props, context: any) {
...
this._fluxContainerSubscriptions = new FluxContainerSubscriptions();
// 初始化当前Stores, 并监听Stores变化
this._fluxContainerSubscriptions.setStores(getStores(props, context));
// 向订阅列表中添加视图更新回调
this._fluxContainerSubscriptions.addListener(() => {
this.setState((prevState, currentProps) =>
getState(prevState, currentProps, context),
);
});
...
}
...
}
三、总结
Flux中有好几个监听事件, 容易导致混乱, 这里总结梳理一下
1、 初始化: FluxReduceStore 和 FluxStoreGroup 分别将各自的回调函数注册到 Dispatcher 的 callback 列表中, 用于订阅 Store 的变化
2、 当页面中的事件触发某个 action 时, action 会去轮训 Disptacher 注册表, 并按顺序逐个出发回调函数
3、 Dispatcher 优先触发单个 Store 的 callback, 即根据 action 判断是否需要更新 Store 对应的 state, 从而决定是否需要触发 change 更新事件, 假设状态有变化, 则广播 change 事件
4、 单个 Store 接收到 change 事件, 则将 Store 的变化状态写进自己的 changed 标识位, 并作为 FluxStoreGroup 订阅事件的输入
5、 所有单个 Store 的 callback 调用结束后, Dispatcher 最后会触发FluxStoreGroup 的 callback
6、 此时 FluxStoreGroup 会根据 changed 标识位的情况, 选择是否要触发视图的更新, 即是否调用 setState
以上就是 Flux 的一些介绍解析, 其采用集中式的单向数据流的监听机制, 管理着 React 组件的状态, 使得状态的变化和视图的更新得以收拢, 便于管理. 可以看到 Flux 与 React 是强耦合的, 是特定场景的产物, 只适用于 React 技术框架, 而且官方说还处在维护模式下, 如果希望更加完善或与框架解耦的解决方案, 可以考虑使用 Redux、MobX、Recoil