Reflux原理与源码详解

<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依照这个逻辑,继续执行。

这里我们简单做一个总结。

策略逻辑遇到插队时是否继续执行
joinStrictstrict抛出错误
joinLeadingfirst执行第一个
joinTrailinglast执行后一个
joinConcatall都会执行

这四种策略,都定义在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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值