Flux

参考:https://blog.andrewray.me/flux-for-stupid-people/

http://www.ruanyifeng.com/blog/2016/01/flux.html

https://github.com/facebook/flux/tree/master/examples

概念

Flux为了解决MVC的局限性(view和model之间的数据流混乱、多个局部更新导致整体页面逻辑复杂)而总结出来的一套基于dispatcher的架构模式。

这种页面状态管理的思路应用在单页应用这种状态庞大的系统中效果尤为明显。

ps:参考这里 前端 MV*模式 中可以发现对MVC有两种理解,这里针对“model的set方法可以被view直接调用”这种观点来讨论。

这种架构中,实际是在MVC的思想上,在model和view相互调用之间穿插一层dispatcher

Flux架构中数据都是单向流动的。有四个组成部分,分别是Dispatcher、Store、Action和View。

《深入React技术栈》P196中提到Flux是要配合Controller View 来实现的,这种实现方式下,所有的状态都由Controller来管理,数据的流向是向下的,页面更新也在Controller中触发,会重新执行render,页面是整体更新的,但因为有react 的 virtual dom,可以高效率更新。这就解决了MVC的局限性

Dispatcher

Dispatcher是一个事件系统,用于广播和回调事件,从flux中获取。接收Action,并且把Action传递给Store(Store在Dispatcher中注册了事件监听),Dispatcher是一个单例。

var Dispatcher = require('flux').Dispatcher;
var AppDispatcher = new Dispatcher();

Action

Action代表了我们要做什么,里面有action的名字以及要用的数据,在FSA(Flux Standard Action)中规定Action必须要有一个type字段,可以拥有其他error、payload和meta等数据字段,此外,不能有其他含义的字段。在view中发送Action如下:

AppDispatcher.dispatch({
    actionName: 'new-item',
    newItem: { name: 'Marco' } // example data
});

View

把store的数据展示出来,在view中要订阅store emit 出来的 change 事件,这样view才能re-render界面。

用户交互的时候,就通过Dispatcher发出Action

Store

Store用于存放逻辑和数据,他是一个单例,在整个应用程序中只会有一个,不需要new。在store定义完后,在Dispatcher中注册监听。

在Store中的数据只能在响应Action的时候修改,所以不应该有对外的setter方法,而是只有getter

var ListStore = …

AppDispatcher.register( function( payload ) {
    switch( payload.actionName ) {
        case 'new-item':
            ListStore.items.push( payload.newItem );
            break;
    }
});

store不是model,它包含model,在整个应用程序中,只有store知道如何更新数据,并且只能在store中才可以注册dispatch回调

View处理界面交互,然后通过dispatch把action传递给store,store更新数据,store更新完了数据之后,需要通知外界,但这个过程并非使用dispatch,而使用其他的如继承eventEmitter(继承的方式可以使用Object.assign):

case 'new-item':
    ListStore.items.push( payload.newItem );
    // Tell the world we changed!
    ListStore.trigger( 'change' );
    break;

然后再在view中响应以上的事件:(记得在unmount的时候解除监听)

componentDidMount: function() {  
    ListStore.bind( 'change', this.listChanged );
}

把数据设置进state中,触发render

  listChanged: function () {
    this.setState({
      items: ListStore.getAll()
    });
  }

Action Creator,以上是在view中直接调用dispatch,直接写action对象,更好的做法是调用Action Creator来实现这些过程:

ListActions = {
    add: function( item ) {
        AppDispatcher.dispatch({
            eventName: 'new-item',
            newItem: item
        });
    }
};

例子:表单校验,所有的表单项都放在一个store中,提交、校验等操作都视为一个个Action。在action creator中需要对store进行修改的时候才发送action,否则如果校验等逻辑,就放在action creator中,校验后得出结果了,再发送action。或者对于其他例子,在action creator中先访问网络,在callback中调用dispatch发送action

 

总结以上过程大概如下:

  view处理用户交互,把action传递给dispatch,dispatcher把action传递给store。

  store是一个单例,创建的时候往dispatch注册好对应事件的回调,并且在回调中修改store的数据。数据修改完成通过类似eventEmitter对象把更新的消息传递给view(这里的实现过程可以令store继承eventEmitter,这样在view的didMounted事件中对store进行注册 如 store.on(xx,...)即可)

  view在以上的更新事件中设置state,更新UI,render中读取state

 flux-todomvc

  添加一个item的逻辑如下:

  输入框的value,通过props获取。绑定onChange事件,调用dispatcher发送action,把input新的值(evt.target.value)传递给了store,store在reduce中把这个值返回,默认的areEqual检测发现当前state与刚刚reduce的返回值不相同,触发render,view中再次读取store的值(调用getState),记录到的props中。

  以上这个过程实现了双向绑定(视图与组件的props相互关联),接着执行添加逻辑,因为双向绑定保证props的值总是最新的,所以获取props的值,通过dispatcher发送给store进行更新,store触发re-render,界面更新出来。

  刚刚是饶了一大圈的双向绑定。回顾简单一点的:组件内部的state记录input的值,onChange中把evt.target.value通过setState更新到state中,render中的value读取的是state中的值。这样的双向绑定保证了state总是最新的。

posted @ 2017-12-11 17:47  HelloHello233  阅读(449)  评论(0编辑  收藏  举报