Flux
Flux 是 Facebook 在 2014 年提出的 Web 应用架构,其核心思想是将应用划分为 action 层、dispatcher 层、store 层和 view 层。
整个应用的运行方式是,用户页面交互行为触发特定类型的 action,dispatcher 会携带特定的 action type 和数据到 store 层,store 层会处理不同 action 类型下的数据变更逻辑,再把变动更新到 view 层。
源码分析
Actions
Actions 就是一个描述操作类型和携带数据的对象,可以设计为一个 Action Creator;
var todoDispatcher = new Dispatcher();
var Actions = {
addTodo(text) {
todoDispatcher.dispatch({
type: 'ADD_TODO',
text
});
}
}
Dispatcher
Dispatcher 在 Flux 架构中扮演的角色是连接 Actions 和 Stores,Dispatcher 提供两个方法,dispatcher.register 用于注册处理 state 变更和 view 更新的逻辑,dispatcher.dispatch 用于触发 dispatcher 注册的各个回调;
Dispatcher 在 Flux 架构中是一个单例,所有 stores 都绑定在一个 dispatcher;
var _prefix = 'ID_';
class Dispatcher {
constructor() {
this._isDispatching = false;
this.isPending = {};
this._isHandled = {};
this._pendingPayload = null;
this._lastID = 1;
this._callbacks = {};
}
register(callback) {
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
return id;
}
// 同一个 payload 将会作为所有 callback 的 payload
dispatch(payload) {
this._startDispatching(payload);
try {
for (var id in this._callbacks) {
if (this._isPending[id]) continue;
this._invokeCallback(id);
}
} finally {
this._stopDispatching();
}
}
_startDispatching(payload) {
for (var id in this._callbacks) {
this._isPending[id] = false;
this._isHandled[id] = false;
}
this._pendingPayload = payload;
this._isDispatching = true;
}
_stopDispatching() {
delete this._pendingPayload;
this._isDispatching = false;
}
_invokeCallack(id) {
this._isPending[id] = true;
this._callbacks[id](this._pendingPayload);
}
}
class TodoStore extends ReduceStore {
constructor() {
super(todoDispatcher);
}
getInitialState() {
// todo
return null;
}
reduce(state, action) {
switch(action.type) {
case 'ADD_TODO':
if (!action.text) return state;
const id = uuid.v4();
return state.set(id, {
id,
text: action.text,
});
default:
return state;
}
}
}
export default new TodoStore();
Store
Flux 架构在创建业务 Store 的时候,将全局单例 dispatcher 和 store 进行绑定,并在 dispatcher 中注册回调函数;
dispatcher.dispatch 调用时,store 中注册的回调函数会触发,遍历和调用 dispatcher._callbacks 的所有回调;
回调函数会调用 store.reduce 方法,如果产生新的 state,则会发送变更事件;
注意,这里的 reduce 是一个纯函数,当 state 发生变化时,会返回一个新的 Immutable 对象,如果没有发生变化则返回原来的 Immutable 对象,根据 Immutable 对象是否发生变化触发后续渲染更新;
Store 可以根据业务分割不同的特定 Store
// ReduceStore
class FlexReduceStore extends FluxStore {
constructor(dispatch) {
super(dispatch);
this._state = this.getInitialState();
}
getState() {
return this._state;
}
/**
* Checks if two versions of state are the same. You do not need to override
* this if your state is immutable.
*/
areEqual(one: TState, two: TState): boolean {
return one === two;
}
// 抽象方法
getInitialState() {}
// 抽象方法
reduce() {}
__invokeOnDispatch() {
this.__changed = false;
const startingState = this._state;
const endingState = this.reduce(startingState, action);
if (!this.artEqual(startingState, endingState)) {
this._state = endingState;
this.__emitChanged();
}
if (this.__changed) {
this.__emitter.emit(this.__changedEvent);
}
}
}
// FluxStore
class FluxStore {
constructor(dispatcher) {
this.__changed = false;
this.__changeEvent = 'change';
this.__dispatcher = dispatcher;
this.__emitter = new EventEmitter;
this._dispatchToken = dispatcher.register((payload) => {
this.__invokeOnDispatch(payload);
});
}
getDispatchToken() {
return this._dispatchToken;
}
addListener(callback) {
return this.__emitter.addListener(this.__changedEvent, callback);
}
__invokeOnDispatch(payload) {
this.__changed = false;
this.__onDispatch(payload);
if (this.__changed) {
this.__emitter.emit(this.__changedEvent);
}
}
// 抽象方法
__onDispatch(action) {
}
__emitChange() {
this.__changed = true;
}
}
Container
绑定 View 和 Stores 的 controller-view,使得经过 dispatcher.dispatch 后变化的数据能够传递给 view 并进行更新
function getStores() {
return [
todoStore,
];
}
function getState() {
return {
todos: todoStore.getState(),
onAdd: todoActions.addToDo,
}
}
Container.createFunctional(AppView, getStores, getState);
// createFunctional
function createFunctional(viewFn, getStores, calculateState) {
class FunctionalContainer extends React.Component {
state = null;
static getStores(props, context) {
return getStores(props, context);
}
static calculateState(prevState, props, context) {
return calculateState(prevState, props, context);
}
render() {
return viewFn(this.state)
}
}
return create(FunctionalContainer);
}
// create
function create(Base) {
const getState = (state, maybeProps, maybeContext) => {
return Base.calculateState(state, maybeProps, maybeContext);
}
const getStores = (maybeProps, maybeContext) => {
return Base.getStores(maybeProps, maybeContext);
}
class ContainerClass extends Base {
_fluxContainerSubscriptions: null;
constructor(props, context) {
super(props, context);
this._fluxContainerSubscriptions = new FluxContainerSubscriptions();
this._fluxContainerSubscriptions.setStores(getStores(props, context));
// *核心*
this._fluxContainerSubscriptions.addListener(() => {
this.setState((prevState, currentProps) => getState(
prevState,
currentProps,
context,
));
});
const calculatedState = getState(undefined, props, context);
this.state = {
...(this.state || {}),
...calculatedState,
}
}
componentWillUnmount() {
if (super.componentWillUnmount) {
super.componenetWillUnmount();
}
this._fluxContainerSubscriptions.reset();
}
}
const container = ContainerClass;
return container;
}
// FluxContainerSubscriptions
class FluxContainerSubscriptions {
_callbacks;
_stores;
_tokens;
_storeGroup;
constructor() {
this._callbacks = [];
}
setStores(stores) {
this._stores = stores;
this._resetTokens();
this._resetStoreGroup();
let changed = false;
let changedStores = [];
const setChanged = () => { changed = true; }
this._tokens = store.map(store => store.addListener(setChanged));
const callCallbacks = () => {
if (changed) {
this._callbacks.forEach(fn => fn());
changed = false;
}
}
this._storeGroup = new FluxStoreGroup(stores, callCallbacks);
}
addListener(fn) {
this._callbacks.push(fn);
}
reset() {
this._resetTokens();
this._resetStoreGroup();
this._resetCallbacks();
this._resetStores();
}
_resetTokens() {
if (this._tokens) {
this._tokens.forEach(token => token.remove());
this._tokens = null;
}
}
_resetStoreGroup() {
if (this._storeGroup) {
this._storeGroup.release();
this._storeGroup = null;
}
}
_resetCallbacks() {
this._callbacks = [];
}
_resetStores() {
this._stores = null;
}
}
// FluxStoreGroup
class FluxStoreGroup {
constructor(stores, callback) {
this._dispatcher = _getUniformDispatcher(stores);
var storeTokens = stores.map(store => store.getDispatchToken());
this._dispatchToken = this._dispatcher.register(payload => {
this._dispatcher.waitFor(storeTokens);
callback();
});
}
release() {
this._dispatcher.unregister(this._dispatchToken);
}
}
function _getUniformDispatcher() {
var dispatcher = stores[0].getDispatcher();
return dispatcher;
}
Flux 运行机制
Dispatcher
Dispatcher 是全局单例,多个 stores 和单一 dispatcher 绑定,每实例化一个 store,都会向 dispatcher 注册对应的回调函数;
dispatcher.dispatch
遍历 dispatcher._callbacks,会调用所有注册的回调函数
所有注册的回调函数都是一样的,就是 store.__invokeOnDispatch(payload)
如何知道当前 action 应该正确触发那个 store.reduce 生成新的 ,通过对应 store 的 store.reduce 中的 action.type 进行匹配。
new Store()
- 每实例化一个 Store,会 dispatcher.register 一个回调函数
- 回调函数会放入 dispatcher._callbacks 对象中
- 回调函数部署的是 store.__invokeOnDispatch(payload) 方法
store.__invokeOnDispatch(payload)
- 调用 store.reduce(prevState, action) 方法,计算出更新后的 state
- 如果数据发生改变,则通过 store.__emiiter.emit() 发送 change 事件
Container
function getStores() {
return [store]
}
function getState() {
return {
todos: store.getState(),
}
}
- 通过 Container.createFunctional(AppView, getStores, getState) 将 stores 和 view 绑定在一起
- 内部通过 ContainerClass 生成 view,ContainerClass 在 constructor 通过 FluxContainerSubscriptions 部署订阅 stores 变更的回调函数
FluxContainerSubscriptions (sub)
- view 在初始化时,通过 sub.setStores(stores) 部署事件回调函数
- 通过 this._tokens = stores.map(store => store.addListener(setChanged)),标识 sub 实例状态是否发生变化,只有在 changed = true 的情况下,绑定在 sub 上的更新 view 的更新逻辑才会触发
- store.addListener(callback) 内部部署 store.__emitter.addListener('change', callback) 方法,当 store.reduce() 方法调用后 state 发生变化,会通知给到 sub,并设置 changed 为 true;
- view 更新回调函数部署在 callCallback 函数,并托管在 FluxStoreGroup 实例
FluxStoreGroup
- FluxStoreGroup 会额外将更新视图的逻辑 callCallback 注册到 dispatcher;
- 并且需要等到其他注册到 dispatcher 的 stores 回调函数调用后,才会进行 callCallback()
- 只有先等到其他注册到 dispatcher 的 stores 回调函数调用,才能知道 state 是否发生改变,从而决定是否更新视图
总的来看,Flux 架构通过 Container 将 stores 和 view 绑定在一起;每个 stores 在实例化的时候,都会向全局单例 dispatcher 注册自身的 reduce,store.reduce 定义了针对不同 actions ,state 所作出的改变;此外,Container 在初始化时,还会额外将更新视图的逻辑 component.setState 注册到 dispatcher 中,将所有的 stores 注册 change 事件的回调函数,用于后续再 state 发生变化时,设置 changed 这一状态锁,这一状态锁用于控制是否进行视图渲染;当 view 视图发生交互,通过 dispatcher.dispatch 提交特定 action 时,dispatcher 会遍历先前注册好的回调列表,回调列表中的函数会调用 store.reduce 计算出 new state,然后会比较前后 state,如果 state 发生变化,store 则会发送 change 事件;change 事件会使得 changed 设置为 true,因此在遍历到更新视图的回调函数时,会开启视图渲染;
Immutable 对象
Flux 架构中,采用 Immutable 对象进行状态前后的比较。
之所以采用 Immutable 对象,是因为在比较前后状态变化的时候,需要快速识别出状态是否产生变化,最快的比较方式是采用浅比较(引用比较);
Immutable 对象的特点就是 Immutable 对象一旦创建就无法修改,如果数据发生变化会产生一个新的 Immutable 对象,如果没有发生变化则会返回原来的 Immutable 对象的引用,因此非常好的契合上述场景。此外,Immutable 对象修改时所创建的新的对象采用优化算法,开销较小,并且有利于实现变化追踪技术。
Immutable 对象被视为值,所以 Immutable 对象在进行 === 比较时是值比较;
// 如果 Immutable 对象未发生改变,则 Immutable 对象是相等的
const map1 = Map({a: 1, b: 2, c: {d: 1}});
const map2 = Map({a: 1, b: 2, c: {d: 1}});
const map3 = map1.set('a', 1);
map1.equals(map2); // true
map1 === map2; // true
map3 === map1; // true 由于值为发生变化
// 如果值发生变化,则 Immutable 对象返回一个新的 Immutable 对象
const map4 = map1.set('a', 2);
map4 === map1; // false
map1.get('a'); // 1
map1.set('a', 1);
map1.getIn(['a', 'c', 'd']); // 1
map1.setIn(['a', 'c', 'd'], 2); // {a: 1, b: 2, c: {d: 2}}
map1.updateIn(['a', 'c', 'd'], value => value + 1); // {a: 1, b: 2, c: {d: 2}}