6
原文地址:https://blog.andrewray.me/flu... ,作者:Andrew Ray

TL;DR 当我在努力学习Flux时,我希望有人告诉我:它并不简单,也没有好的文档可以查,并且有许多变化的部分。

我需要使用Flux吗?

如果你的应用程序需要处理动态数据(dynamic data)的话,那么答案就是yes,你可能需要使用Flux。

如果你的应用程序仅仅是无需共享状态静态视图(static view),并且你从不保存也不更新数据,那么你不需要使用Flux,Flux不会给你带来任何好处。

为什么是Flux?

皮一下,因为Flux是个适度复杂的主意,为啥要增加复杂度呢?

90%的iOS应用程序是表格视图中的数据。iOS工具包具有良好定义的视图和数据模型可以让应用开发变得简单。

但是在前端(Font End:HTML,JS,CSS),我们甚至都没有。相反,我们遇到一个很大的问题:没有人知道应该如何去构建一个前端应用。我从事这个行业多年,从来没人教给我“最佳实践”,相反,他们教了我好多“库(libraries)”,诸如jQuery,Angular,Backbone等等。但是真正的问题、数据流,仍然避开了我们。

什么是Flux?

Flux是一个用来描述具有非常特定事件和监听的“单向”数据流的词。没有官方的Flux库,但是你需要Flux Dispatcher和任何的JavaScript event library

官方文档写的就像某人的意识流一样,从这里开始学习是不太好的。但是一旦你掌握了Flux,它可以帮助你填补空白。

不要试图把Flux同MVC架构进行比较,它们的相似之处只会令人困惑。

正式入坑!我将按顺序解释概念,并且一个一个地构建它们。

1.视图的“Dispatch”和“Actions”

Dispatcher(调度员)本质上是一个加入了额外规则的事件系统。它来广播事件并注册回调。全局的dispatcher只有唯一的一个,你应该使用Facebook Dispatcher Library。实例化非常容易:

var AppDispatcher = new Dispatcher();  

假设你的应用程序有一个“新建”按钮来向列表添加项目。

<button onClick={ this.createNewItem }>New Item</button>  

点击会发生什么?你的视图会调度一个非常具体的“操作”,其中包含操作名称和新项目数据:

createNewItem: function( evt ) {

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

}

action”是Facebook创造的另一个词。它是一个JavaScript对象,用以描述我们想要做什么事情,以及做这件事我们需要的数据。正如你所见到的,我们要做的事情就是添加一个new-item,我们需要的数据就是项目name

2."Store"响应调度的操作

像Flux一样,“Store”这个词也是Facebook创造的.对于我们的应用程序,我们需要列表的特定逻辑和数据集合。这描述了我们的Store,我们称之为ListStore。

Store是一个单体对象,意味着你可能不能通过“new”关键字来声明它,应用程序中每个Store里只有一个实例。

// Single object representing list data and logic
var ListStore = {

    // Actual collection of model data
    items: []

};

然后,Store会响应已分派的操作:

var ListStore = …

// Tell the dispatcher we want to listen for *any*
// dispatched events
AppDispatcher.register( function( payload ) {

    switch( payload.actionName ) {

        // Do we know how to handle this action?
        case 'new-item':

            // We get to mutate data!
            ListStore.items.push( payload.newItem );
            break;

    }

}); 

这是Flux处理调度回调的传统方式。每个payload包含一个action的名称(actionName)和数据(newItem),switch语句确定Store是否应该响应action,并且知道根据action的类型处理数据变化。

?关键点:store不是数据模型一个Store包含模型

?关键点:store在你的应用程序中唯一知道如何更新数据的东西,它是Flux中最重要的部分。我们调度的action并不知道如何添加或者删除项目。

举个栗子,假如应用程序中不同的部分需要保持跟踪某些图片及其元数据,那么你就需要创建其他的store,并将其命名为ImageStore。一个store相当于应用程序中一个单独的“域(domain)”,如果应用程序非常庞大,这些域可能对你来说已经很明显了。如果应用程序很小,你可能只需要一个store。一般来说,一种模型类型只对应一个Store。

只有store允许注册Dispatcher的回调!view永远不应该调用AppDispatcher.register。Dispatcher应该只用于将消息从视图View发送到Store。视图(view)会响应不同类型的事件。

3. Store触发“Change”事件

即将完成!现在数据确实已经变化了,我们需要告诉全世界!
Store触发一个事件(Event),但是不会使用dispatcher。这虽然令人困惑,但是这就是Flux的方式。让我们给我们的Store加入触发事件的能力。如果你正在使用MicroEvent.js,那么很简单:

MicroEvent.mixin( ListStore );  

然后,触发changes事件

case 'new-item':

            ListStore.items.push( payload.newItem );

            // Tell the world we changed!
            ListStore.trigger( 'change' );

            break;
?关键点:当我们触发事件的时候,我们不会传递最新的项目。视图View只关心有事情发生变化了。让我们继续关注数据以了解原因。

4. 视图(View)响应“Change”事件

现在我们需要展示列表。当列表发生变化时,视图会完全地重新渲染(re-render)。

首先,当组件“安装(mount)”时,即组件首次被创建的时候,从ListStore中监听change事件:

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

为简单起见,我们只调用forceUpdate,它可以触发重新渲染(re-render)

listChanged: function() {  
    // Since the list changed, trigger a new render.
    this.forceUpdate();
},

当组件“卸载(unmount)”的时候,不要忘记清除事件监听器

componentWillUnmount: function() {  
    ListStore.unbind( 'change', this.listChanged );
},

现在怎么办?让我们来看看我的render函数,我故意将其保存到最后。

render: function() {

    // Remember, ListStore is global!
    // There's no need to pass it around
    var items = ListStore.getAll();

    // Build list items markup by looping
    // over the entire list
    var itemHtml = items.map( function( listItem ) {

        // "key" is important, should be a unique
        // identifier for each list item
        return <li key={ listItem.id }>
            { listItem.name }
          </li>;

    });

    return <div>
        <ul>
            { itemHtml }
        </ul>

        <button onClick={ this.createNewItem }>New Item</button>

    </div>;
}

现在已经完整了。当你添加新项目的时,View 发出用户的 Action,Dispatcher 收到 Action,要求 Store 进行相应的更新,store改变数据,然后store会触发change事件,最后视图通过重新渲染页面来响应change事件。


译者注:原文无此图

但这里有一个问题:每次列表更改时我们都会重新渲染整个视图!这不是非常低效吗?

不。

当然,我们将再次调用render函数,并确保渲染函数中的所有代码都将重新运行。但是,如果渲染输出已更改,React将仅更新真实DOM。您的render函数实际上是生成一个“虚拟DOM”,React与之前的输出进行比较render。如果两个虚拟DOM不同,React将仅使用差异更新真实DOM.

?关键点:当Store的数据改变时,视图不应该关心是否有东西被添加,删除,或是被改变了。视图应该只去做重新渲染。React 的“虚拟DOM”差异算法会去处理这些重大问题,找出那些真正发生变化的DOM节点。这会让您的生活更加简单,并降低您的血压。

还有一件事:“Action Creator”到底是个啥?

记住,当我们点击按钮的时候,会分配一个具体的动作(action):

AppDispatcher.dispatch({  
    eventName: 'new-item',
    newItem: { name: 'Samantha' }
});

好吧,如果您的许多视图需要发送此操作,则可以重复输入。此外,您的所有视图都需要知道特定的对象格式。那太蹩脚了。Flux建议一种抽象,称为Action Creator,它只是将上述内容抽象为一个函数。

ListActions = {

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

};

现在您的视图可以调用ListActions.add({ name: '...' });,而不必担心调度的对象语法。

PS:不要使用forceUpdate

我因为习惯了forceUpdate这个简单的缘故。组件读取store数据的正确方法,是将数据拷贝到state,并且在render函数中读取this.state。您可以在TodoMVC example中看到它的工作原理。

首次加载组件时,store的数据被拷贝到state,当store更新时候,数据被完整地重新拷贝。这样做是更好的,因为在内部,forceUpdate是同步的,同时setState的效率也是非常高的。


司楠
174 声望5 粉丝

热爱生活的Web前端攻城狮