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.jsconst 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.jsconst 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.jsconst 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.jsxconst 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虽然繁琐一些,但是一板一眼,对于前端程序员比较容易理解。