Flux分析与实践
缘起:
在做了接近一年的React小程序开发之后,关于程序的状态管理框架,有了一些思考上的变化。项目一开始技术选型是用的Mobx,原因是当时参与选型的同学认为Redux大量的模板代码和学习曲线不适合,这在当时的背景下这并没有什么问题(项目成员都是Native客户端出身,对于前端的技术栈并没有深入了解,加上当时项目的排期非常紧张),但是随着后来团队的调整,开始了漫长的一个人维护整个项目代码的艰难历程,因为项目本身的复杂度,开始对Mobx的感觉发生了一些变化,从一开始的放弃Redux选择Mobx是没有任何理由的正确决定,到后面发觉,结合业务场景来看,Mobx可能并不是一个好的解决方案。当然个人并没有在实际项目中用过Redux,不能直接说二者谁好谁差,因此,我希望在个人的视角上,重新注意并学习和理解Redux,看看Redux是否能够解决目前Mobx存在的一些问题,既是查漏补缺,也算是复盘整个大半年的维护经历。
对于Redux的学习研究,主要以《Redux实战》这本书为例。
因为非专业前端,很多东西仍然要从最简单的开始,比如提到Redux,就不得不提到Flux。
正文:
前端无论是Web,还是Android,架构理念发展了这么多年,个人认为核心点之一还是在于,如何将View层和Model层的关系进行清晰明了,由此诞生了MVC,MVP,MVVM等开发框架,框架会要求我们应该在哪一部分写什么代码,当然,这里传统Android的MVC和Web的MVC的实践上其实是有出入的,不过理念都是一样的。
关于MVC的理念图,并不止一种,如上所示,这些都是MVC或者MVC的变种,但是规范而言,我们希望不要在View层中去直接操作Model层,Model层的变化通过Controller层去触发,然后Model层变更之后再通知到View层更新视图。
不过,规范如果不强制一点,往往后来人就容易不遵守规范,快速的业务迭代和排期压力使得很多或者不少时候,大家都是怎么快速怎么来,我能在View层中直接改Model层,为啥还需要去通过Controller干这事儿,因此,屎山便来了。
而关于Flux的理念图:
- View 视图层
- Store 数据管理器
- Dispatcher 事件分发器,用于派发Action
- Action 动作,用于交互或其他View层操作
则比较清晰的定义了一个范式,即Flux程序应该怎么去写,Store的更新只能是通过Dispatcher去操作,然后Store再通知View层渲染新视图。
不过Flux太老了,也没有必要重新开始学,找一个demo琢磨一下,搞清楚整个工作流程,为Redux学习做好铺垫就行了。
这里我选择的demo是:GitHub - ruanyf/extremely-simple-flux-demo: Learn Flux from an extremely simple demo
关于工程结构:
MyButtonController就是页面展示的内容。
MyButton是一个Component,内部放了一个ul标签和button标签,点击button的时候,往ul里边增加一个item。
button的点击事件:
var MyButtonController = React.createClass({
getInitialState: function () {
return {
items: ListStore.getAll()
};
},
// 组件挂载的时候注册监听
componentDidMount: function() {
ListStore.addChangeListener(this._onChange);
},
// 组件卸载的时候解绑监听
componentWillUnmount: function() {
ListStore.removeChangeListener(this._onChange);
},
// 监听回调,重新setState触发视图的重新渲染
_onChange: function () {
this.setState({
items: ListStore.getAll()
});
},
// 点击事件,触发ButtonActions的addNewItem纯函数调用
createNewItem: function (event) {
ButtonActions.addNewItem('new item');
},
render: function() {
return <MyButton
items={this.state.items}
onClick={this.createNewItem}
/>;
}
});
ButtonActions中定义了种种action,每一个action都是一个函数
var ButtonActions = {
addNewItem: function (text) {
AppDispatcher.dispatch({
actionType: 'ADD_NEW_ITEM',
text: text
});
},
};
addNewItem转发了一个对象给AppDispatcher的dispatch方法,这个对象包含两个内容,一个是action的类型,另一个是action传递的内容。
var AppDispatcher = new Dispatcher();
AppDispatcher.register(function (action) {
switch(action.actionType) {
case 'ADD_NEW_ITEM':
ListStore.addNewItemHandler(action.text);
ListStore.emitChange();
break;
default:
// no op
}
})
AppDispatcher是一个全局的Dispatcher对象,Dispatcher的实现后面再分析,总而言之,这里往Dispatcher中注册了一个callback,后面再通过AppDispatcher分发时间的时候,switch到对应的actionType,就会走对应的case,在里边完成Store的更新,即:
ListStore.addNewItemHandler(action.text);
ListStore.emitChange();
我们再看ListStore的实现:
var ListStore = assign({}, EventEmitter.prototype, {
items: [],
getAll: function () {
return this.items;
},
// 添加一条text
addNewItemHandler: function (text) {
this.items.push(text);
},
// 提交change事件
emitChange: function () {
this.emit('change');
},
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
ListStore是一个EventEmitter对象,在添加完一条text之后,APPDispatcher中还调用了emitChange方法,通过EventEmitter的能力,发送了一个change事件。
而在MyButtonController组件挂载的时候,往ListStore中添加了对change事件的监听,因此最终,调用到MyButtonController的_onChange方法,通过setState,完成视图的更新。
让我们再回到这张图:
在整个工作流程中:
View层点击事件触发了一个Action,Action将会通过Dispatcher进行分发,然后执行对应的逻辑,去更新Store中的数据,Store更新完了之后,触发View的setState,进行视图重新渲染。
EventEmitter
EventEmmiter是NodeJS中的一个模块,作用和EventBus类似。在上面的ListStore中,我们用到了EventEmitter的emit,on,removeListener函数:
EventEmitter/EventEmitter.js at master · Olical/EventEmitter · GitHub
;(function (exports) {
'use strict';
function EventEmitter() {}
// Shortcuts to improve speed and size
var proto = EventEmitter.prototype;
var originalGlobalValue = exports.EventEmitter;
function indexOfListener(listeners, listener) {
var i = listeners.length;
while (i--) {
if (listeners[i].listener === listener) {
return i;
}
}
return -1;
}
// 支持通过别名调用某个函数
function alias(name) {
return function aliasClosure() {
return this[name].apply(this, arguments);
};
}
proto.getListeners = function getListeners(evt) {
var events = this._getEvents();
var response;
var key;
// Return a concatenated array of all matching events if
// the selector is a regular expression.
if (evt instanceof RegExp) {
response = {};
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
response[key] = events[key];
}
}
}
else {
response = events[evt] || (events[evt] = []);
}
return response;
};
/**
* Takes a list of listener objects and flattens it into a list of listener functions.
*
* @param {Object[]} listeners Raw listener objects.
* @return {Function[]} Just the listener functions.
*/
proto.flattenListeners = function flattenListeners(listeners) {
var flatListeners = [];
var i;
for (i = 0; i < listeners.length; i += 1) {
flatListeners.push(listeners[i].listener);
}
return flatListeners;
};
/**
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Object} All listener functions for an event in an object.
*/
proto.getListenersAsObject = function getListenersAsObject(evt) {
var listeners = this.getListeners(evt);
var response;
if (listeners instanceof Array) {
response = {};
response[evt] = listeners;
}
return response || listeners;
};
function isValidListener (listener) {
if (typeof listener === 'function' || listener instanceof RegExp) {
return true
} else if (listener && typeof listener === 'object') {
return isValidListener(listener.listener)
} else {
return false
}
}
/**
* 注册一个事件监听器
*/
proto.addListener = function addListener(evt, listener) {
if (!isValidListener(listener)) {
throw new TypeError('listener must be a function');
}
var listeners = this.getListenersAsObject(evt);
var listenerIsWrapped = typeof listener === 'object';
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
listeners[key].push(listenerIsWrapped ? listener : {
listener: listener,
once: false
});
}
}
return this;
};
// 别名调用addListener函数
proto.on = alias('addListener');
// ......
//
proto.removeListener = function removeListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var index;
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
index = indexOfListener(listeners[key], listener);
if (index !== -1) {
listeners[key].splice(index, 1);
}
}
}
return this;
};
/**
* Alias of removeListener
*/
proto.off = alias('removeListener');
/**
* 提交一个事件,事件提交之后,所有监听该事件的回调都会被执行
*/
proto.emitEvent = function emitEvent(evt, args) {
var listenersMap = this.getListenersAsObject(evt);
var listeners;
var listener;
var i;
var key;
var response;
for (key in listenersMap) {
if (listenersMap.hasOwnProperty(key)) {
listeners = listenersMap[key].slice(0);
for (i = 0; i < listeners.length; i++) {
// If the listener returns true then it shall be removed from the event
// The function is executed either with a basic call or an apply if there is an args array
listener = listeners[i];
if (listener.once === true) {
this.removeListener(evt, listener.listener);
}
response = listener.listener.apply(this, args || []);
if (response === this._getOnceReturnValue()) {
this.removeListener(evt, listener.listener);
}
}
}
}
return this;
};
/**
* Alias of emitEvent
*/
proto.trigger = alias('emitEvent');
proto.emit = function emit(evt) {
// 把参数提取出来
var args = Array.prototype.slice.call(arguments, 1);
return this.emitEvent(evt, args);
};
// ......
}(typeof window !== 'undefined' ? window : this || {}));
那从源码来看,其实EventEmitter做的事情就是提供事件总线的能力,这样在Store完成数据变更之后,通过发送对应的事件,就能触发View层中的Component去执行对应的setState完成视图的更新。
Dispatcher
需要注意Flux只是一个开发规范,所以Flux的一些实现,并不唯一,我们来看下Dispatcher的实现:
import { _classCallCheck, invariant } from './util'
class Dispatcher {
constructor() {
_classCallCheck(this, Dispatcher)
this._callbacks = {}; // 回调map
this._isDispatching = false; // 是否在派发action
this._isHandled = {}; // 完成态
this._isPending = {}; // 等待态
this._lastID = 1; // 回调map序号
}
// 扔一个回调过去
register (callback) {
const _prefix = 'ID_';
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
return id;
}
// 根据id 删除回调字典某个方法
unregister (id) {
!this._callbacks[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.unregister(...): `%s` does not map to a registered callback.', id) : invariant(false) : undefined;
delete this._callbacks[id];
}
// 当dispatch(action)时,会按照callback的register顺序依次触发
// 当如果callback中调用了waitFor方法,则会优先处理具有相应id的callback
// 但要注意得是 :当两个store互相等待时,会进入死锁状态,即等待的id即处于处理态(pending)
// 但也未完成(!isHandled),这个时候会抛出异常(开发环境) 生产环境则直接跳过
waitFor (ids) {
!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): Must be invoked while dispatching.') : invariant(false) : undefined;
for (var ii = 0; ii < ids.length; ii++) {
var id = ids[ii];
if (this._isPending[id]) {
!this._isHandled[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): Circular dependency detected while ' + 'waiting for `%s`.', id) : invariant(false) : undefined;
continue;
}
!this._callbacks[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): `%s` does not map to a registered callback.', id) : invariant(false) : undefined;
this._invokeCallback(id);
}
}
// 分发事件
dispatch (payload) {
!!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.') : invariant(false) : undefined;
// js本身是单线程的,用一个成员变量来记录payload参数即可
this._startDispatching(payload);
try {
// 遍历所有的callback
for (var id in this._callbacks) {
if (this._isPending[id]) {
continue;
}
// 执行每一个callback
this._invokeCallback(id);
}
} finally {
this._stopDispatching();
}
}
isDispatching () {
return this._isDispatching;
}
_invokeCallback (id) {
this._isPending[id] = true;
// 因为_callbacks是一个map,通过id取出来的就是一个个的function,直接调用传入payload即可
this._callbacks[id](this._pendingPayload);
this._isHandled[id] = true;
}
_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;
}
}
export default Dispatcher
总结
Flux的原理其实很简单,更多的是提供一种单向数据流的开发规范。