提要:本文所展示示例代码,既非JQuery代码,也是不React代码,为介于两者间的,方便行文伪代码。代码意思通过上下文即可理解。
Facebook的Jing Chen同学在2014年的F8上演示了她的FLUX
架构。
FLUX
的工作流如下:
- 中央Dispatcher管理所有进来的ACTION。
- 可以注册任意多个Store处理ACTION.
- 组件在需要修改数据的时候,发出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;
}
});
【完】
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。