再见Flux, 欢迎Bacon/Rx?

Facebook的Flux和React.js刚刚变得很火,大有剿灭MVC之势,现在又有人提出再见Flux,新的Bacon/Rx有哪些优势呢?

Facebook一年前引入Flux架构,它是客户端建立Web应用的最新革新,是对Angular.js的前端MVC的提升与革命,如今已经成为Web开发者最火热的技术。

Flux是使用dispathcer将业务逻辑从用户界面中分离出来,核心思想是单向数据流,这意味着响应用户动作的事件的传播可以单向传遍整个系统,不需要绑定任何内部数据模型:

这种单向数据流与React的虚拟DOM绑定,使得Flux实现更加简洁,因为没有必要进行状态区分(虚拟DOM框架实现这个一功能),但是Flux是重量的,它引入了多个模板(如 事件发射器、监听器等等),有一些企业软件的繁琐酸味,如按约定编程与分层结构。

函数响应式编程Functional Reactive Programming (FRP) 是一种新的编程范式,事件建模为事件流,事件流是类似一个不变的数组,它们能够被map 过滤 combine和merge,数组和事件流的区别是事件流中的事件是异步的,每次当事件发生时,它会被通过流传播,最终被订阅者消费使用。

顾名思义,Reactive响应式编程是React的本质,发生的动作通过事件流传播,这些事件流结合起来形成应用的状态,当事件通过系统传播以后,新的应用状态对象导致了状态的订阅者,它会通过根级别的React组件重新渲染:


整个状态广播的逻辑能使用下面代码实现,使用的是Bacon.js作为FRP库:


// app.js
const React = require('react'),
Bacon = require('baconjs'),
TodoApp = require('./todoApp'),
todos = require('./todos'),
filter = require('./filter')

const filterP = filter.toProperty(<initial filter>),
itemsP = todos.toItemsProperty(<initial items>, filterP)

const appState = Bacon.combineTemplate({
items: itemsP,
filter: filterP
})

相比Flux,这样做的好处是不再需要分离动作和存储,事件流非常简单,取而代之是一个业务组件,它有一个公共API通过本地dispatcher与业务逻辑进行通讯:


// todos.js
const Bacon = require('baconjs'),
Dispatcher = require('./dispatcher')

const d = new Dispatcher()

module.exports = {
toItemsProperty: function(initialItems, filterP) {
// "business logic"
const itemsP = Bacon.update(initialItems,
[d.stream('remove')], removeItem,
[d.stream('create')], createItem,
...
)
return Bacon
.combineAsArray([itemsP, filterP])
.map(setItemsDisplayStatusBasedOnFilter)

function createItem(items, newItemTitle) {
return items.concat([{<new item data>}])
}

function removeItem(items, itemIdToRemove) {
return items.filter(it => it.id !== itemIdToRemove)
}
...
},

// "public API"

createItem: function(title) {
d.push('create', title)
},
removeItem: function(itemId) {
d.push('remove', itemId)
},

...
}

dispatcher.js是一个可发布的事件流,事件流能够被订阅和消费:


// dispatcher.js
const Bacon = require('baconjs')

module.exports = function() {
const busCache = {}
// Bus == Subject in Rx

this.stream = function(name) {
return bus(name)
}
this.push = function(name, value) {
bus(name).push(value)
}
this.plug = function(name, value) {
bus(name).plug(value)
}

function bus(name) {
return busCache[name] = busCache[name] || new Bacon.Bus()
}
}

视图代码如下,视图会变得简单,没有回调,监听者,React的虚拟DOM会做剩余的工作,我们只要同步公共API去调用业务逻辑:


// todoItem.jsx
const React = require('react'),
todos = require('./todos')

module.exports = React.createClass({
render: function() {
const item = this.props.item
return (
<li className={item.states.join(' ')}>
<div className=
"view">
<label>{item.name}</label>
...
<button
className=
"destroy"
onClick={() => todos.removeItem(item.id)}
/>
</div>
</li>
)
}
})

以上代码演示见:TodoMVC project

Good bye Flux, welcome Bacon/Rx? — Medium

点评:Bacon/Rx的事件流类似事件总线,虽然很简单,但是理解起来有一定难度,而Flux虽然繁琐一些,但是一板一眼,对于前端程序员比较容易理解。

一个月以后肯定有个帖子《再见Bacon/Rx》