1

提要:本文所展示示例代码,既非JQuery代码,也是不React代码,为介于两者间的,方便行文伪代码。代码意思通过上下文即可理解。

Facebook的Jing Chen同学在2014年的F8上演示了她的FLUX架构。

clipboard.png

FLUX的工作流如下:

  1. 中央Dispatcher管理所有进来的ACTION。
  2. 可以注册任意多个Store处理ACTION.
  3. 组件在需要修改数据的时候,发出ACTION.

但是,如果只是上述那个简单的话,就实在没什么好说的了。举个例子,如果我有一个TodoList,在点击添加item的时候,需要把内容发送到服务器,根据服务器返回的结果,显示成功或失败的结果,那么采用FLUX则是这么写的:

javascript
dispatcher.register(function(payload){ switch(payload.actionType) { case "ADD_ITEM": ajax("/add/item", payload.data, function success(res){ dispatcher.dispatch("ADD_SUCCESS", payload.data); }, function failure(){ dispatcher.dispatch("ADD_FAIL", payload.data); }); break; case "ADD_SUCESS": // do something break; case "ADD_FAIL" // do something break; } }); // 在组件中发出 $("#add").click(function(){ dispatcher.dispatch({ actionType: "ADD_ITEM", data: $("input").val() }); });

上述代码是有问题的,因为在Store中发出异步请求,然后在回调中又发出ACTION,这种情况属于Nested Update,导致的问题有:

  • 你不知道你发出的action,什么时候才会处理完
  • 你不知道你的事件,数据的流向在哪儿
  • 容易引发循环调用

当然,最最终导致的结果就是:难以维护。

因此,在FLUX中,首要的一个原则就是:

STORE代码同步化

上面的代码可以改成:

dispatcher.register(function(payload){
    switch(payload.actionType) {
        case "ADD_ITEM":
            // 显示小菊花,表示"正在加载"
            break;
        case "ADD_SUCESS":
            // 显示加载成功
            break;
        case "ADD_FAIL"
            // 显示加载失败
            break;
    }
});

$("#add").click(function(){
    var msg = $("input").val();
    dispatcher.dispatch({
        actionType:"ADD_ITEM", 
        data: msg
    });

    ajax('add/item', msg, function success(res) {
        dispatcher.dispatch({
            actionType:"ADD_SUCESS",
            data: res
        });
    }, fail(res){
        dispatcher.dispatch({
            actionType:"ADD_FAIL"
            data: res
        });
    });

});

这样在store中的三个case里的操作都是同步的了,即任何action,我们都很清楚其触发了之后,具体发生了什么事情。

注:不要被上面代码中的注释迷惑,Store并不应该去操作DOM,上面的注释只是说明其之后发生了什么。

上面的例子中,我们将ajax请求放在组件中,成功时触发一个action,失败时又触发另一个,使得程序行为是完全可预测的。但如果这个ajax请求的数据非常复杂呢,组件内部就无法发出请求了,因为它没有收集这些数据的能力。

举个例子,在一个购物车页面内,勾选增加/删除某个商品,会:

  • 商品列表改变了
  • 收集所有其它已勾选的商品及数量
  • 发送ajax到后台,计算总价(不要问我为什么,这里涉及大量的满减,积分,立减等碧池逻辑)
  • 后台返回计算后的总价
  • 更新前端

上面的关键步骤发送ajax到后台,我们并不能在商品数量组件中发送,因为单个组件无法收集整个商品列表的信息,如果它可以收集的话,你的程序就不好维护了,因为你的组件强依赖于外部信息。

这种情况似乎又绕回了篇首的写法,因为store中有相应的信息,那么就在store收集数据,发出ajax呀。(又有同学提议说,让组件从Store中取数据,不就能收集到所需数据了吗?)。 注意,FLUX另一原则是:

不要让你的组件强依赖于Store,你组件的数据应该来自于父组件,而不是Store。强依赖于Store的后果是:

* 你的组件难以复用
* 你的组件难以测试

在此例中,我能想到的解法应该是,通过父组件传入回调给子组件处理:

// 购物车中
<Cart>
    <Quantity onChange={function(quantity){
        // 1. 收集所有商品信息
        var info = getAllDealsInfo();
        // 2. 当前商品数量+1
        info[this.id]++;

        // 3. 发出请求
        ajax('/checkTotal', info, function(total) {
            // 当服务器返回时,
            dispatcher.disptach({
                actionType: 'CHANGE_TOTAL',
                total: total
            });
        });
    }} />

</Cart>


//数量组件中
input.on('change', function(){
    // 发出action,可能某个角落有store监听这个action
    // 用以统计当前所有商品的数量
    dispatch.dispatch({
        actionType: "CHANGE_QUANTITY",
        id: this.id,
        quantity: this.value
    });
    // 调用父组件传来的onChange函数
    this.props.onChange();
});

如此,一可保持Store中代码的同步化,二可保持组件中对外部世界的无知。

FLUX另外最重要的特点就是,非常容易扩展。举例而言,在上面购物车例子中,如果我要增加一个区块显示每样商品的数量,我不需要修改原有的代码,只需要新增加一个store即可。

dispatcher.register(function(payload) {
     switch(payload.actionType){
         case "CHANGE_QUANTITY":
             // 1. 收集所有商品的数量
             // 2. update对应的model,更新view
             break;
     }
});

【完】


ssnau
1.5k 声望98 粉丝

负能量职业打码师


引用和评论

0 条评论