<a name="e" href="#e" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
目录
<ul>
<li><a href="#a">看前必读</a></li>
<li><a href="#b">Flux模式介绍</a></li>
<li><a href="#c">Reflux原理分析</a></li>
<li><a href="#d">Reflux源码解读</a>
<ul>
<li><a href="#d_1">发布者和订阅者的公共方法</a>
<ul>
<li><a href="#d_1_1">PublisherMethods</a></li>
<li><a href="#d_1_2">ListenMethods</a></li>
</ul>
</li>
<li><a href="#d_2">创建Action和Store</a>
<ul>
<li><a href="#d_2_1">创建Action的模块</a></li>
<li><a href="#d_2_2">创建Store的模块</a></li>
</ul>
</li>
<li><a href="#d_3">Reflux的发布者队列</a>
<ul>
<li><a href="#d_3_1">joinStrict</a></li>
<li><a href="#d_3_2">joinLeading</a></li>
<li><a href="#d_3_3">joinTrailing</a></li>
<li><a href="#d_3_4">joinConcat</a></li>
</ul>
</li>
<li><a href="#d_4">View的设计</a></li>
</ul>
</li>
</ul>
<a name="a" href="#a" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
看前必读
Reflux是Flux模式的一种具体实现。本文从一开始就分别介绍了Flux模式和Reflux的设计原理。之后,又对源码进行深入剖析,将Reflux拆分成发布者和订阅者的公共方法、Action和Store的实现、发布者队列和View的设计等四个方面,并逐一解读。
<a name="b" href="#b" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
Flux模式介绍
Flux是Facebook提出的一种构建web应用的模式,用以帮助开发者快速整合React中的视图组件。在整个流程中,数据从应用上层到底层,从父组件到子组件,单向流动
(unidirectional data flow)。它由Dispacther、Store、View三个主要部分构成。看下面这张图
╔═════════╗ ╔════════════╗ ╔═══════╗ ╔══════╗
║ Action ║──────>║ Dispatcher ║──────>║ Store ║──────>║ View ║
╚═════════╝ ╚════════════╝ ╚═══════╝ ╚══════╝
^ ╔════════╗ │
└────────── ║ Action ║ ──────────┘
╚════════╝
通过这张图,我们可以大概的了解什么是Flux模式。
- Action收集了视图变更的行为,比如用户点击了按钮、需要定时发送的请求,然后通知Dispatcher
- Dispatcher是一个单例,是一个根据不同Action,触发对应的回调,维护Store
- Store是一个数据中心,只有Store的变化才能直接引发View的变化
- Action一直处于就绪状态,以上三步周而复始
这种设计虽然提高了Store管理的复杂度,但能够使得数据状态变得稳定、可预测。由于Flux不是本文的重点,此处有简化,需要了解更多的话,请访问官网的Flux介绍。
<a name="c" href="#c" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
Reflux原理分析
Reflux是Flux模式的一种实现。不过略有区别。
╔═════════╗ ╔════════╗ ╔═══════╗
║ Action ║──────>║ Store ║──────>║ View ║
╚═════════╝ ╚════════╝ ╚═══════╝
^ │
└─────────────────────────────────┘
Reflux实现了单向数据流,也实现了Flux中提及的Action和Store。它将Action和Dispatcher合并到了一起。Dispatcher不再是一个全局的单例,大大的降低了编码复杂度和维护的难度和复杂度。一个Action就是一个Dipatcher,可以直接引发Store的变化。Store可以监听Action的变化。此外,如果有Store互相依赖的情况,那么Store可以直接监听Store。
说到这里,聪明的你看到我说到“监听”两个字,肯定就大概猜到Reflux的代码大概是怎么写的。没错,Reflux这种设计,就是典型的订阅发布模式。
在Reflux中,每一个Action都是一个发布者Publisher,View是一个订阅者Listener。而Store比较特殊,它监听Action的变化,并引发View的改变,所以它既是一个发布者,又是一个订阅者。
<a name="d" href="#d" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
Reflux源码解读
Reflux的核心代码都在reflux-core这个库文件里面,我们可以通过npm install reflux-core下载到本地。入口文件index.js和其他模块,都在lib文件夹里面。index.js引入了lib下面的大部分文件,并将文件对应的方法挂载在Reflux这个变量下面。大概分成下面几类:
- Reflux的版本信息和公共方法
- 发布者和订阅者的公共方法
- 创建Action和Store
- Reflux的发布者队列
后面三块是Reflux的实现核心,我们后面依次会讲到。
在这些模块中,并没有涉及到View,说明Relfux是一种纯粹的Flux思想的实现方式,可以脱离React与其他的框架一起使用。View的设计,都在refluxjs这个库里。我们可以通过npm install refluxjs下载代码到本地。
<a name="d_1" href="#d_1" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
发布者和订阅者的公共方法
Reflux中的Action、Store、View其实只有两种角色,一个是发布者Publisher,一个是订阅者Listener。于是,Reflux将这两种角色的公共方法抽象成了两个模块PublisherMethods.js和ListenerMethods.js。我们分别来看:
<a name="d_1_1" href="#d_1_1" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
PublisherMethods
这个文件保存了发布者的公共方法,也就是Action和Store作为发布者都有的方法。文件的返回值是一个如下的对象:
module.exports = {
// 触发之前的回调, 在shouldEmit之前执行
preEmit: function(){...},
// 是否能够触发,返回boolean值
shouldEmit: function(){...},
// 设置监听事件,触发后执行
listen: function(){...},
// 当shouldEmit的执行结果为true时,立即执行
trigger: function(){...},
// 当shouldEmit的执行结果为true, 尽快执行
triggerAsync: function(){...},
// 为trigger包裹一层函数defer函数
deferWith: function(){...}
}
- preEmit和shouldEmit
在trigger执行之前,首先会先执行preEmit和shouldEmit回调。preEmit用于修改发布者传过来的参数,并将返回值会传给shouldEmit。由shouldEmit的返回值true或者false判断是否触发。
- listen和trigger
listen方法和trigger方法是配套的。先看listen,里面有两行比较关键:
this.emitter.addListener(this.eventLabel, eventHandler);
...
me.emitter.removeListener(me.eventLabel, eventHandler);
...
我们在trigger这个方法中,看到代码
...
this.emitter.emit(this.eventLabel, args);
...
而this.emitter,在后面我们会看到,他就是EventEmitter的一个实例。EventEmitter这个库,是用作对象注册和触发相关事件的。所以listen和trigger两个方法的意思已经很清楚了。就是listen方法的作用就是注册监听,返回一个可以解除注册事件的函数。而trigger则是触发事件的方法。
- trigger和triggerAsync
这两个方法比较有意思,一个是立即执行,一个是尽快执行。什么意思呢。我们看util.js中的对应代码:
_.nextTick(function () {
me.trigger.apply(me, args);
});
而这个所谓的_.nextTick实际上是这个:
setTimeout(callback, 0);
那么实际上就是:
triggerAsync: function(){
let me = this;
let args = arugments;
setTimeout(function(){
me.trigger.apply(me, args)
}, 0);
}
triggerAsync的设计,主要是为了解决一些异步操作导致的问题。这里我用Uxcore举个例子。在Uxcore的Form有个重置所有的FormField的方法叫resetValue。它的实现原理是这样的:Form本身保存了一份原始值,调用resetValues的时候,会把这份原始值异步赋给各个FormField。所以,如果在下面这个场景中,继续调用trigger,就不会获得预期效果。要改用triggerAsync。
// User.Search用来搜索符合条件的员工
let User = Reflux.createActions({
Search: {
children: ['reset', 'do', ...]
}
});
// 调用resetValues,清空搜索表单的值
User.Search.reset();
// 用初始值搜索一次
// 下面这个不会取得预期效果
// 这个与User.Search.do()效果相同
User.Search.do.trigger();
// 要用这个
// User.Search.do.triggerAsync();
- deferWith
deferWith重写了trigger方法。把之前的trigger保存到变量oldTrigger中,并将其作为第一个参数传递给deferWith的第一个参数callback,剩下的参数依次传递。举个例子,如果我们执行的是
deferWith(fn, a, b, c)
那么,trigger方法就会变成
function(){
fn.apply(this, [oldTrigger, a, b, c]);
}
<a name="d_1_2" href="#d_1_2" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
ListenMethods
这个文件保存了订阅者的公共方法,也就是Store和View作为订阅者都有的方法。文件的返回值是一个如下的对象:
module.exports = {
// 这个是给validateListening使用的工具方法
hasListener: function(){...},
// 多次调用listenTo, 一次性设置多个监听
listenToMany: function(){...},,
// 这个是给listenTo使用的工具方法
// 校验监听函数是否是合法, 比如
// 是否监听自己,是否通过函数监听,是否循环监听
validateListening: function(){...},
// 设置监听函数
listenTo: function(){...},
// 停止监听
stopListeningTo: function(){...},
// 停止所有监听
stopListeningToAll: function(){...},
// 这个是给listenTo使用的工具方法
// 执行发布者的getInitialState方法
// 并以其返回值为参数,执行一个默认的回调defaultCallback
fetchInitialState: function(){...},
//下面这四个方法,就是Reflux中发布者队列了,我们后面来说
joinTrailing: maker("last"),
joinLeading: maker("first"),
joinConcat: maker("all"),
joinStrict: maker("strict")
}
这个文件有一个核心方法,就是listenTo。它连接了发布者和订阅者。我们看源代码:
listenTo: function(listenable, callback, defaultCallback){
...
//订阅者的数组,保存了所有的订阅者信息
subs = this.subscriptions = this.subscriptions || [];
...
subscriptionobj = {
// unsubscriber是一个取消监听的函数,
// 也是stopListeningTo能够取消监听的原因
stop: unsubscriber,
// listenable指的是发布者,就是谁被监听
listenable: listenable
};
// 把subscriptionobj对象push进订阅者数组里
subs.push(subscriptionobj);
return subscriptionobj;
}
<a name="d_2" href="#d_2" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
创建Action和Store
<a name="d_2_1" href="#d_2_1" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
创建Action的模块
Action相关的方法被放在ActionMethods.js和createAction.js两个文件中。另外,index.js文件也定义了同时创建多个Action的createActions方法。
- ActionMethods
ActionMethods这个模块代码只有最简单的一行
module.exports = {};
但是作用可不简单,它给所有的Action设置了公共的方法,可以在你需要的时候随时调用。ActionMethods在index.js中被直接挂在了Reflux下面。所以你可以直接使用。
比如说我们定义一个
Reflux.ActionMethods.alert = function (i) {
alert(i);
};
var showMsg = Reflux.createAction();
那么你可以这么使用:
showMsg.alert('Hello Reflux!');
这样就会直接弹出一个alert框。非常粗暴,也非常实用。
- createAction
我们知道createAction用法有这几个
// 空参数创建
var TodoAction1 = Reflux.createAction();
// 立即执行还是尽快执行
var TodoAction2 = Reflux.createAction({
sync: true
});
// 是否是异步的Action
var TodoAction3 = Reflux.createAction({
asyncResult: true
});
// 设置子方法
var TodoAction4 = Reflux.createAction({
children: ['success', 'warning']
});
// TodoAction5是一个有多个Action的数组
var TodoAction5 = Reflux.createAction(['create', 'retrieve', {update: {sync: true}}]);
...
我们再跟一下源码,看是怎么做的。createAction方法一开始就有两个for循环,用以检验要Action的名称合法性,不能与Reflux.ActionMethods中的方法重名,也不能与已定义过的Action重名,我们假设叫做TodoAction。
源码如下:
var createAction = function createAction(definition) {
...
// 省略校验的代码
...
// 定义子Action
definition.children = definition.children || [];
// 如果是一个异步的操作,那么就额外给其加上两个子Action,completed和failed
if (definition.asyncResult) {
definition.children = definition.children.concat(["completed", "failed"]);
}
// 这里是是个递归,生成所有的子Action
// 将所有的children遍历一遍,为每一个都执行createAction方法
var i = 0,
childActions = {};
for (; i < definition.children.length; i++) {
var name = definition.children[i];
childActions[name] = createAction(name);
}
// 将发布者的公共方法,Action公共的方法和当前要创建的TodoAction的配置merge到一起
var context = _.extend({
eventLabel: "action",
emitter: new _.EventEmitter(),
_isAction: true
}, PublisherMethods, ActionMethods, definition);
// 设置如果把当前要创建的Action TodoAction当做函数直接执行的策略
// 如果sync为true,那么执行TodoAction()就相当于执行TodoAction.trigger()
// 反之,就相当于执行TodoAction.triggerAsync()
var functor = function functor() {
var triggerType = functor.sync ? "trigger" : "triggerAsync";
return functor[triggerType].apply(functor, arguments);
};
//继续合并
_.extend(functor, childActions, context);
//将生成的Action,保存进Keep.createdActions数组里面
Keep.createdActions.push(functor);
return functor;
}
module.exports = createAction;
- createActions
创建多个Action,我们一般有两种用法:
// 参数是数组
var TextActions1 = Reflux.createActions(['create', 'retrieve', 'update', 'delete']);
// 参数是对象
var TextActions2 = Reflux.createActions({
'init': {
sync: true
},
'destroy': {
asyncResult: true
}
});
所以,index.js中的createActions,其实就是判断参数是否是一个数组,如果是,就对每一个数组项都调用一次createAction方法。反之,就当成一个key-value型的对象处理。所有的key都作为Action的名称,所有的value都作为对应Action的配置。
<a name="d_2_2" href="#d_2_2" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
创建Store的模块
Store相关的方法被放在StoreMethods.js和createStore.js两个文件中。
- StoreMethods
StoreMethods这个模块与ActionMethods类似,代码只有最简单的一行
module.exports = {};
但是作用可不简单,它给所有的Store设置了公共的方法。
- createStore
createStore与createAction也很类似。createStore方法一开始也有两个for循环,用以检验要Store的名称合法性,不能与Reflux.StoreMethods中的方法重名,也不能与已定义过的Store重名。我们来看具体的代码:
module.exports = function (definition) {
var StoreMethods = require("./StoreMethods"),
PublisherMethods = require("./PublisherMethods"),
ListenerMethods = require("./ListenerMethods");
// 这里与createAction一样,是校验Store名称的合法性
...
// 这里是Store的核心方法
function Store() {
var i = 0,
arr;
// 同样的 订阅者数组
this.subscriptions = [];
// 这就是我们之前在PublisherMethods中讲过的emitter
this.emitter = new _.EventEmitter();
...
// 如果有init方法,则执行
// 如果没有用listenToMany设置监听方法,那么就需要在init中设置listenTo了
if (this.init && _.isFunction(this.init)) {
this.init();
}
// 如果有订阅的回调,则执行ListenMethods中的方法监听
if (this.listenables) {
arr = [].concat(this.listenables);
for (; i < arr.length; i++) {
this.listenToMany(arr[i]);
}
}
}
// 这里是核心的一步,给Store的原型上merge进订阅者、发布者、Store的公共方法和当前创建的Store的配置
_.extend(Store.prototype, ListenerMethods, PublisherMethods, StoreMethods, definition);
// 实例化Store
var store = new Store();
// 把sotre放入一个公共的数据,方便统一管理
Keep.createdStores.push(store);
return store;
};
<a name="d_3" href="#d_3" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
Reflux的发布者队列
刚才在ListenMethods中,订阅者可以订阅多个发布者的消息,这些发布者形成了一个队列。如果发布者队列遇到插队的问题怎么办呢?举个例子,S顺序订阅了A和B。如果执行完A('a'),B('b')即将执行的时候,用户插入了A('A'),。那么S怎样处理A('a')、A('A')和B('b')的执行结果呢?
Reflux提出了joinTrailing、joinLeading、joinConcat、joinStrict四种处理策略,分别对应了last、first、all、strict四种逻辑,
亦即,执行A('A')->B('b')、A('a')->B('b')、A('a')->A('A')->B('b')、A('a')执行后报错。上一个的执行结果,会传给下一个。
因为这个相对较少使用,我在这里以Action为发布者,Store为监听者为例写一段代码,用以帮助理解。
var A = Reflux.createAction();
var B = Reflux.createAction();
var Store = Reflux.createStore({
init: function() {
let me = this;
// 这里要根据需要设置成不同的策略
me.joinStrict(A, B, me.trigger);
}
});
Store.listen(function() {
console.log('result:', JSON.stringify(arguments));
});
// 测试片段1
//A('a');
//A('A');
//B('b');
//B('B');
// 测试片段2
A('a');
B('b');
A('A');
B('B');
在这段代码中,把A和B形成了一个队列。执行顺序为A->B。对不同策略分别执行测试片段1和测试片段2。
<a name="d_3_1" href="#d_3_1" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
joinStrict
- 测试片段1
Uncaught Error: Strict join failed because listener triggered twice.
result: {"0":["a"],"1":["b"]}
- 测试片段2
result:{"0":["a"],"1":["b"]}
result:{"0":["A"],"1":["B"]}
- 结论
A->B之间,如果插入了A,就会执行第一个A,同时抛出一个错误,停止执行。
<a name="d_3_2" href="#d_3_2" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
joinLeading
- 测试片段1
result: {"0":["a"],"1":["b"]}
- 测试片段2
result: {"0":["a"],"1":["b"]}
result: {"0":["A"],"1":["B"]}
- 结论
A->B之间,如果插入了A,就执行第一个A,跳过后面的。 第一个A的执行结果,作为参数传递给B。B依照这个逻辑,继续执行。
<a name="d_3_3" href="#d_3_3" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
joinTrailing
- 测试片段1
result: {"0":["A"],"1":["b"]}
- 测试片段2
result: {"0":["a"],"1":["b"]}
result: {"0":["A"],"1":["B"]}
- 结论
A->B之间,如果插入了A,就执行后一个A,跳过前面的。 后一个A的执行结果,作为参数传递给B。B依照这个逻辑,继续执行。
<a name="d_3_4" href="#d_3_4" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
joinConcat
- 测试片段1
result: {"0":[["a"],["A"]],"1":[["b"]]}
- 测试片段2
result: {"0":[["a"]],"1":[["b"]]}
result: {"0":[["A"]],"1":[["B"]]}
- 结论
A->B之间,如果插入了A,就再执行一次A。 两个A的执行结果,放到一个数组里面,作为参数都传递给B。B依照这个逻辑,继续执行。
这里我们简单做一个总结。
策略 | 逻辑 | 遇到插队时 | 是否继续执行 |
---|---|---|---|
joinStrict | strict | 抛出错误 | 否 |
joinLeading | first | 执行第一个 | 是 |
joinTrailing | last | 执行后一个 | 是 |
joinConcat | all | 都会执行 | 是 |
这四种策略,都定义在joins.js文件里面。我们看一段核心代码:
// 返回一个函数
// 该函数根据不同的策略,确定不同的后面监听函数的参数
function newListener(i, join) {
return function () {
var callargs = slice.call(arguments);
// 对应的监听若果尚未被触发,就根据相应的策略来确定该监听的参数
if (join.listenablesEmitted[i]) {
switch (join.strategy) {
// 如果是strict的,则只能执行一次,抛出错误
case "strict":
throw new Error("Strict join failed because listener triggered twice.");
// 如果是last的,则监听函数的参数就为该函数的参数
case "last":
join.args[i] = callargs;break;
// 如果是all的,则监听函数的参数是之前执行过的所有监听的返回值构成的数组
case "all":
join.args[i].push(callargs);
}
} else {
// 设置监听已触发
join.listenablesEmitted[i] = true;
join.args[i] = join.strategy === "all" ? [callargs] : callargs;
}
// 所有的监听都触发后执行join.callback,并重置队列
// 这里打个断点,可以帮助我们更好的理解上面的示例代码
emitIfAllListenablesEmitted(join);
};
}
...
发布者队列类似于Flux模式中的waitFor设计,具有非常广泛的使用场景:
- 请求完一个接口后,继续请求一个接口
- 新手引导
- 先出现loading提示,再请求接口,最后取消loading或者显示loaded
- 一个的处理结果,需要等待另一个的处理结果
- ...
<a name="d_4" href="#d_4" class="headeranchor-link" aria-hidden="true">
<span class="headeranchor"></span>
</a>
View的设计
我们前文分析过,View是一个订阅者。那么View就要有ListenerMethods的所有方法。因为我们的View层是基于React框架的,那么订阅和发布d的消息,应该在对应的生命周期里发生。源码中也确实是这么实现的。
在实际使用中,我们一般通过mixins,将Reflux和React联系在一起。这样,Reflux就可以在React对应的生命周期执行对应的操作。下面依旧从refluxjs的入口文件src/index.js分析。index.js中,也给Reflux变量挂载了几个方法。这几个方法在设计上是比较雷同的,一般是分两步。第一步,是在componentDidMount的时候,注册监听;第二步,则是在componentWillUnmount的时候,移除所有的监听。我们分开来看。
- ListenerMixin
ListenerMixin是View其他方法所共用的,类似ListenerMethods。
...
module.exports = _.extend({
componentWillUnmount: ListenerMethods.stopListeningToAll
}, ListenerMethods);
它返回一个merge了ListenerMethods的对象。这个对象明确要求,组件要卸载(移除)的时候取消所有注册的监听。
- listenTo
listenTo方法将某个Store与组件的某个方法关联起来。当Store变化时,就调用设置的回调callback。
...
// 这里的三个参数实际上就是
// 要监听的 store
// store 变化后要执行的回调 callback
// initial 是计算完初始值后执行的回调(一般不需要)
// 这个就是刚才fetchInitialState中说到的回调defaultCallback
module.exports = function(listenable,callback,initial){
return {
...
componentDidMount: function() {
...
// 通过 listenTo 注册监听
this.listenTo(listenable,callback,initial);
},
...
// 通过 stopListeningToAll 取消所有监听
componentWillUnmount: ListenerMethods.stopListeningToAll
};
};
listenTo方法的实现方式很简单了,在组件加载完成的时候,注册监听,在组件要卸载的时候,取消监听。
- listenToMany
listenToMany与listenTo基本一样。区别就是listenToMany调用了ListenerMethods的listenToMany,可以同时注册多个监听。
module.exports = function(listenables){
return {
componentDidMount: function() {
...
// 通过 listenToMany 注册监听
this.listenToMany(listenables);
},
...
// 通过 stopListeningToAll 取消所有监听
componentWillUnmount: ListenerMethods.stopListeningToAll
};
};
- connect
connect方法可以将组件的某一部分state,与指定的Store上。当Store变化的时候,组件的state也同步更新。
// listenable 指的就是要监听的store
// key 则为与store绑定后,需要变化的state[key]的key
// 也就是说,store变化后,state[key]也同步变化
module.exports = function(listenable, key) {
// 如果事件没有key,则直接报错
_.throwIf(typeof(key) === 'undefined', 'Reflux.connect() requires a key.');
return {
// 获取state初始值
// 因为是mixin到React中的,所以比React中的getInitialState要先执行
getInitialState: function() {
...
},
componentDidMount: function() {
var me = this;
// 依然是给React 混入ListenerMethods的方法
_.extend(me, ListenerMethods);
// 设置监听
this.listenTo(listenable, function(v) {
me.setState(_.object([key],[v]));
});
},
// 这里其实就是取消所有的监听
componentWillUnmount: ListenerMixin.componentWillUnmount
};
};
- connectFilter
connectFilter与connect设计思路基本类似,只不过每次在state的值被被setState前,都会执行一个filterFunc函数来做处理。connectFilter的设计,既能够帮助开发人员保护state不被污染,又能够减少不必要的更新。
module.exports = function(listenable, key, filterFunc) {
// 省略部分是校验key值的合法性
...
return {
// 获取state初始值
getInitialState: function() {
...
// 这里是与上一节的connect方法不同的地方
// 在返回state的之前,先执行filterFunc函数
var result = filterFunc.call(this, listenable.getInitialState());
...
},
componentDidMount: function() {
...
this.listenTo(listenable, function(value) {
// setState前先处理
var result = filterFunc.call(me, value);
me.setState(_.object([key], [result]));
});
},
// 取消所有的监听
componentWillUnmount: ListenerMixin.componentWillUnmount
};
};
本文到这里就要结束了,谢谢大家的阅读。如果有任何意见或者建议或者想成为我同事带我飞的,请发邮件给我wenzhao.fw@alibaba-inc.com。