再见Flux, 欢迎Bacon/Rx?

15-05-12 banq
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
})
<p>

相比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)
  },

  ... 
}
<p>

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()
  }
}
<p>

视图代码如下,视图会变得简单,没有回调,监听者,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>
    )
  }
})
<p>

以上代码演示见:TodoMVC project

Good bye Flux, welcome Bacon/Rx? — Medium

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

                   

2
lostalien
2015-05-12 19:34
一个月以后肯定有个帖子《再见Bacon/Rx》

猜你喜欢