净化

写给小白Flux教程

净化 · 2017-02-09翻译 · 433阅读 原文链接

写给小白Flux教程

作为一个小白,Flux很不直观,也没有很多入门级文档,没有明确的解释。在我摸索学习 Flux 的时候,有人能告诉我下面这些东西。

本文是写给小白ReactJS教程的续篇。

我该用Flux吗?

如果你的应用使用了 动态数据 那么 你应该用Flux

如果你的页面只是 静态页面 不共享状态,你不需要保存和上传数据,你不需要用Flux,Flux不会给你带来好处。

为什么是Flux?

幽默的说,因为Flux是一个相对难懂,复杂的概念。为什么要增加复杂度呢?

90% 的IOS应用把 数据传入table view IOS封装了很好的视图组件,数据模型,让开发者轻松的开发。

在前端开发中(HTML, Javascript, CSS),我们没有这样的概念。也就是说我们要面对一个大问题: 我们不知道该如何构建一个前端应用程序。我入行多年,没有人探讨“最佳实践”。反而是探讨类库,比如jQuery,Angular,Backbone,但是我们逃避了最重要的问题,数据流。

什么是Flux?

Flux 是一个名词,主要描述一种用特别的事件和监听器处理数据流的方式。这里没有官方的Flux类库,但是你需要用到 Flux Dispatcherevent library

官方文档写的好像某个人脑子里的意识流,这并不是一个适合作为初学者起步的文档,只有当深入了解Flux时,才能理解作者,脑补作者没写的内容。

不要试图比较 Flux 和 MVC 架构,这只会让你更加混淆。

让我们开始吧,这次我将 逐个解释概念

1. Views层"Dispatch" "Actions"(分发操作)

dispatcher本质是一个添加了额外规则的事件系统。它分发事件并定义回调函数。 只能定义一个全局的dispatcher。你可以使用Facebook的类库 Dispatcher Library。举个例子:

var AppDispatcher = new Dispatcher();

比方说有一个“新建”按钮,点击按钮把item添加到一个列表中。

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

点击后会发生什么?View 层触发一个特定事件,包括item名和item的数据:

createNewItem: function( evt ) {

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

}

"action" 是Facebook创造的另一个词。action一个Javascript对象,表示我们想做的动作,和我们需要的数据。如上所述,我们想创造一个new-item,我们需要的数据是 name


2. "Store" 响应 Dispatched Actions (被分发的事件)

Flux中"store" 也是一个Facebook创造的名词。我们的应用中,我们需要一个集合存放状态,我们叫它ListStore。

Store 就是一个单例,意味着你不需要 new 出来。ListStore 就是一个全局的对象:

// 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 包含一个事件名和数据。使用 switch 语句来决定调用哪个操作,是响应回调,还是改变数据,还是其他。

核心观点: Store 不是 model,而是 model 的容器。

核心观点: 应用中唯一知道如何更新数据的就是 Store。这是 Flux 最重要的一部分。我们分发的事件是不知如何添加或者删除条item。

再比如,应用的其他部分需要保存图片和它们的元数据,你需要使用另外一个 Store,可以取名为 ImageStore。一个 Store 就代表了应用的一个单独的域。如果应用足够大,那么下呢按划分区域。如果应用不大,那很可能只需要一个 Store。

只有 Store 可以注册 callback。View 永远都不应该调用 AppDispatcher.register。Dispatcher 的存在就是为了把消息从 View 传递到 Store。而 View 则是响应另外一种事件。

3. Store 触发 “change” 事件

现在数据已经变化了,我们要通知其他部分。

Store 触发一个事件,但不使用 Dispatcher。这也许会让你混淆,不过这就是 Flux 的方式。下面我们给Store添加event,如果你使用 MicroEvent.js ,可以这么写:

MicroEvent.mixin( ListStore );

接下来就是触发事件:

 case 'new-item':

            ListStore.items.push( payload.newItem );

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

            break;

核心观点: 就单单触发事件,不传递最新的item。View 只关心是不是有内容改变了。下面我会说一下原因。

4. View 层响应 “change” 事件

现在我们需要展示列表了,当列表变化的时候,View会完全重绘

首先,当 Component “上马”的时候,监听 ListStore 的 change 事件,就是在 Component 创建时:

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

简单点,直接调用 forceUpdate,触发重绘。另外一种方式,就是把整个列表保存在 state 中。

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

当 Component加载完时,记得清除监听函数,也就是在 Component 调取回调函数的时候:

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>;
}

整个流程就是这样。当你添加以一个item,View item触发一个行为,Store 对这个行为作出相应,Store 更新,Store 触发 change 事件,接着 View 响应这个 change 事件重绘。

但是有一个问题:每次列表变化的时候都重绘整个 View,那样效率低的可怕。

不是。

没错,render 函数确实被调用了,render 函数中所有代码都一次次的执行了。但是只有在 DOM 真正变化的时候 React 才把变化 render 出来render 函数生成了一个虚拟DOM,React 会对比之前一次 render 出来的 DOM。如果这两次的虚拟DOM不一样,React 就更新真实 DOM 中变化的部分。

核心观点:当 Store 变化时,View 无需关心条目是添加、删除,还是修改了。它只需要整个重绘,React 的“虚拟DOM”算法进行复杂的运算,找出哪些真实的DOM节点变化了。这可以帮助你简单生活,不再紧张。

补充: 该死的 "Action Creator" 是什么?

你应该还记得,点击按钮,触发事件:

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

不过,如果 View 中有很多地方都需要触发这个事件,这就太冗余了。而且,所有的 View 都需要知道事件对象的特定格式。这样有些别扭。Flux 提出一个抽象的层,叫做行为action creators(创建器),其实就是把上面的代码放到一个函数中。

ListActions = {

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

};

现在 View 只需要调用ListActions.add({ name: '...' });,不用关心分发对象的语法了。

没有回答的问题

所有关于Flux的文章,大多告诉我们如何管理数据流,但是并没有解答如下问题:

  • 如何加载,保存数据并更新到服务端?

  • 在不用父组件的情况下,组件之间如何通信?

  • 我应当使用哪个events library?这是否很重要?

  • 为什么Facebook没有把Flux相关的类库打包成一个类库?

  • 我是否可以使用一个数据层代替store,比如Backbone的model?

这些问题的答案是: 开心就好!

PS: 不要使用强制刷新

我使用了 强制更新 的目的仅仅是为了简单举例。正确的更新视图方法是,读store里的数据, 复制数据到state通过读this.state 再调用render函数更新。你可以看这个例子。 TodoMVC example.

当模板第一次加载时, store数据被复制state. 当store更新, 数据再次被完全复制。这样会更好,因为它只在内部, 强制更新是同步的,通过setState效率更高。

以上!

作为附加资源,可以查看Facebook提供的Example Flux Application 。希望看过这个 the files in the js/ folder will be easier to understand.

Flux文档包含一些高级技巧,还有一些深层的内容。

还有一个关于Flux的例子,参照ReactJS Controller View Pattern.

如果这篇文章帮助你理解了Flux, 可以考虑关注我Twitterbuying me a coffee :)

相关文章