react ReFlux细说 - Go语言中文社区

react ReFlux细说


转载出处:http://www.cnblogs.com/lovesueee/p/4893218.html

Flux作为一种应用架构(application architecture)或是设计模式(pattern),阐述的是单向数据流(a unidirectional data flow)的思想,并不是一个框架(framework)或者库(library)。

前言


在细说Flux之前,还是得提一下React ,毕竟Flux这个名字,是因为它才逐渐进入到大众视野。

React是facebook提出来的一个库,用来构建用户界面(User Interface),它的三大特点(来自官方):

  1. JUST THE UI: 仅仅是一个View(components),可以认为是MVC中V,用来构建UI界面。
  2. VIRTUAL DOM : 虚拟dom,为的是:高性能dom渲染(利用diff算法)、组件化(向web components看齐)、多端同构(node,react native)。
  3. DATA FLOW: 单向数据流(one-way data flow),指的是:一种自上而下的渲染方式(top-down rendering)。

总而言之,对于一个react web应用,它的UI将会由无数个组件(react component)嵌套组合而成,它们之间存在着层级(hierarchy)关系(通过JSX的语法糖可以轻易看出),也就因此有了父组件,子组件和顶层组件的概念。

然而就像上述第一点所说,React仅仅是一个View,对于一个web应用,没有数据就显得毫无意义。

现在,假使我们通过一个WebAPI模块取得了数据,那么如何传递给React 组件(components),从而实现UI渲染呢?结合组件的层级关系,想到上述所说的第三点:自上而下的渲染,我们将数据传递给顶层组件(controller-view),同样作为父组件的它,便可以通过组件的属性(properties)将一些有用数据传递给它的各个子组件(各取所需数据),就这样一级一级自上而下地传递下去(直到每一个叶子组件),最终,每一个组件都将得到自己渲染所需要的数据,从而完成UI的渲染。

那么,倘若此时数据变化了(比如:对于一个列表而言,用户点击删除按钮,删除了一条数据),我们又该如何通知各个组件进行UI更新呢?

有这样一种清晰的思路:

  • 首先,我们应该需要一个数据存储(Store),存储着react web应用当前的状态(State),就像MVC中的Model一样。
  • 然后,当用户点击删除按钮时,将会触发一个消息(Action),告诉Store数据变化了,以及哪里变化了(payload)。
  • 最后,Store修改了数据之后,再将新的数据传递给最顶层组件,重新完成一次自上而下的渲染(re-render),从而更新了UI(不要过分担心性能问题,VIRTUAL DOM就是用来解决这个的)。

显然上述的几步,React作为一个View是不可能做到的,也正因为这样,Flux作为一种架构方案才被提出来,它的思想大体就是上述这几步,通过一个单向数据的流动,完成了UI的更新,用一张图可以表示,如下(以Facebook Flux为例):

a unidirectional data flow

当然,作为应用数据处理的模式,除了Flux,还有很多(如:传统的MVC,MVVM),只是Flux凭借其单向数据流特点,使得数据流变得简单,易于调试和追踪问题,所以更适合与React进行组合使用。

前面,我们就一直在说,Flux是一种架构,一种模式,并不是一个框架,也不是一个库,就像我们说MVC(VM)的概念一样,所以,遵循着Flux模式所阐述的思想自然就会出现一些库,如:Facebook FluxRefluxFluxxorRedux等等。

本文主要讲解的Reflux,不过在这之前还是需要先提一下Facebook Flux,从而为后面一些对比做一些铺垫。

Facebook Flux


Facebook Flux,是Facebook在提出Flux架构后,给出的一个对Flux的简单实现,可以认为是Flux库的第一个范例,所以,也有人称之为Original Flux

Facebook Flux中引入了四个概念: Dispatcher、 ActionsStoresViews(Controller-Views),而它们之间的关系就如同上面的那张图所描述的一样,构成了一个单向数据流的闭环,简化版如下:

facebook flux dataflow

接下来,将以官方的TodoMVC Demo为例,来说明它们各自的作用,以及它们之间是如何配合工作的?(PS:建议读者将源代码clone下来,边看边调试)

Facebook Flux Demo


Views and Controller-Views

Facebook Flux中所指的Views,其实就是React Components,用作UI渲染,而相对特别的,Controller-Views指的则是顶层React Component,除了UI渲染外,它还负责接收来自Store变化的数据,并传递给它的Child Component(即Controller-View -> Child Views),用于子View的渲染。

在这个例子中,TodoApp就是一个Controller-View,它监听到TodoStore的数据变化后,便会重新从TodoStore中获取数据,然后通过调用组件setState()方法,触发render()方法的执行,从而得到UI的更新(自上而下的渲染)。

// 从TodoStore中获取数据
function getTodoState() {
  return {
    allTodos: TodoStore.getAll(),
    areAllComplete: TodoStore.areAllComplete()
  };
}

var TodoApp = React.createClass({

  componentDidMount: function() {
    // TodoApp监听TodoStore的数据变化
    TodoStore.addChangeListener(this._onChange);
  },

  render: function() {
    return (
        <div>{/* 此处代码省去 */}</div>
    );
  },

  _onChange: function() {
    // 重新获取TodoStore的数据,并通过调用setState,触发re-render
    this.setState(getTodoState());
  }

});

Stores

Facebook Flux中的Stores,作为数据存储的模块,类似于MVC中的Model,它负责接收Dispatcher分发过来的actions,针对不同的actionType,对数据就进行不同的操作(如:增删改查),最后再通知View,数据变化了,需要进行UI更新。

在这个例子中,TodoStore通过变量_todos变量存储着整个应用的数据(一个列表),并通过AppDispatcher(Dispatcher实例)注册回调,来接收不同类型的Action指令,进而执行不同的数据操作(mutate data),最后通知TodoApp View数据改变,需要更新UI(re-render)。

// 数据存储(一个列表)
var _todos = {};

// 操作数据的函数
function create(text) {/*此处代码省去*/}
function update(id, updates) {/*此处代码省去*/}
function destroy(id) {
  delete _todos[id];
}

// 接收分发过来的Action
AppDispatcher.register(function(action) {
  var text;
  
  // 判断Action类型,采取不同的数据操作
  switch(action.actionType) {
  
    // 新增
    case TodoConstants.TODO_CREATE:
      text = action.text.trim();
      if (text !== '') {
        create(text);  // 创建数据,并存储
        TodoStore.emitChange(); // 通知TodoApp数据变化,需要更新UI
      }
      break;
      
    // 更新
    case TodoConstants.TODO_UPDATE_TEXT:/*此处代码省去*/
      break;
      
    // 删除
    case TodoConstants.TODO_DESTROY:/*此处代码省去*/
      break;
      
   /*此处省去部分代码*/
  }
});

Dispatcher

Facebook Flux中,Dispatcher起到了一个中央枢纽(Central Hub)的角色,它存储着一张Stores列表清单,并且负责Actions的分发工作,即Action的一旦触发,Dispatcher将会通知列表清单上的所有的Stores,每一个Store则选择性地针对该Action进行特定处理(或者不处理)。

在一个应用中,Dispatcher实例只允许有一个(Single),也就是说它将作为一个单例而存在。

在这个例子中,AppDispatcher就是这样一个单例,我们在TodoStores通过AppDispatcher.register()注册回调(见上段代码),来接收不同类型的Actions(消息订阅),在TodoActions里通过AppDispatcher.dispatch()执行不同Actions的分发(消息发布),如下:

var TodoActions = {
  // 新增Action
  create: function(text) {
    AppDispatcher.dispatch({  // 通知TodoStore对数据进行修改(带有Action类型和关联数据)
      actionType: TodoConstants.TODO_CREATE, // Action类型:create
      text: text  // 传递给TodoStore的数据
    });
  },
  // 更新Action
  updateText: function(id, text) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_UPDATE_TEXT, // Action类型:update
      id: id,
      text: text
    });
  },
  // 删除Action
  destroy: function(id) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_DESTROY, // Action类型:destroy
      id: id
    });
  }
  
  /*此处省去部分代码*/
};

Actions

Facebook Flux中的有一个概念叫做Action Creator,可以将它理解为一个方法(即helper method),专门用来创建某种类型的Action。

上一段代码中,TodoActions模块就提供了这些helper methods(或者叫做Action Creators),如:

  • TodoActions.create(text)
  • TodoActions.updateText(id, text)
  • TodoActions.destroy(id)
  • ...

上述每一个方法在内部,都定义了自己的常量类型(actionType),并且将接收的参数作为数据(payload),从而封装成一个完整的Action(即actionType + payload = Action)。

最后,再统一通过调用Dispatcher.dispatch()将特定的Action以消息的形式分发出去(即传递给Stores),Stores在得到Action后,便可以通过Action.actionType来判定采取某种操作(或者忽略这个Action),而执行操作时需要用到的数据则来自Action.payload


思考

Facebook Flux中提出的这四个概念,承担着各自角色,通过互相协作,形成了一个单向数据流的闭环。
------【推荐大家看下这篇文章《A cartoon guide to Flux》,生动形象地描述了这几个角色。】

说完了Facebook Flux,让我们静静思考一下,存在的不足:

倘若,有一个单页面应用,程序中就可能存在N个store,每个store都会监听1~N个action,代码就会像这样:

// storeA.js
Dispatcher.register(function (action) {
    switch(action.actionType) {
        case 'actionA': break;
        case 'actionB': break;

        /* ... 1~N个action */
        
        case 'actionN': break;
    }
});

// storeB.js
Dispatcher.register(function () {
    // 同上
});

/* ... */
/* ... 1~N个store */
/* ... */

// storeN.js
Dispatcher.register(function () {
    // 同上
});

假使此时,触发了一个actionX,那么storeA~storeB的通过Dispatcher.register()注册的回调函数会按注册顺序依次被触发(无一例外),也就是说每个store都会得到actionX通知,唯一不同的可能就是:每个store模块,会通过各自的switch语句进行判断,有的对actionX做处理,有的则不处理(忽略),那么问题来了:

『既然有些store对actionX不需要处理,那么它们注册的回调执行是否有必要?毕竟是函数执行是有开销的,如果有1000个store对actionX不"感冒"的的话,会不会很浪费资源?』

分析下这个问题:Facebook Flux是以Dispatcher(发布者)作为消息中枢,所有的Action消息都会统一从这里分发出去,广播给所有的Store(订阅者),也就是说:发布者(Dispatcher)和订阅者(Stores)之间存在着一对多的关系,而事实上Actions(消息)和Stores(订阅者)之间却存在着一个多对多的关系,如下图:

enter image description here

这样的矛盾,就使得,每一个Store不得不在自己的回调函数里通过Switch语句,来判断当前Action的类型,来决定要不要进行处理,那么暂且抛开性能不说,显然,这样写法,却显得繁重且不够优雅。

于是,接下来,看看Reflux在Facebook Flux的基础之上,做了那些优化?

Reflux


Reflux,是另一个实现Flux模式的库,旨在使整个应用架构变得更加简单

准确地说,Reflux是由Facebook Flux演变而来(inspired by Facebook Flux),可以说是它的一个进化版本,自然而言就会拿两者进行比较:详见这里

简要概括一下重点,就是:

1.Reflux保留了Facebook Flux中原有的三个概念:ActionsStoresViews(Controller-Views),去除了Dispatcher,如果要用一张图表示的话,就是这样:

reflux data flow

此时会有人问:没有了消息中枢(Dispatcher),消息Actions如何发布出去,并传递到Stores呢?

答:在Reflux中,每一个Action本身就是一个Publisher(消息发布者),即自带了消息发布功能;而每一个Store除了作为数据存储之外,它还是一个Subscriber,或者叫做Listener(消息订阅者),自然就可以通过监听Action,来获取到变化的数据。

2.Store之间可以互相监听

这样的场景还是有的,比如:在单页面应用中,如果不同Page拥有不同的Store,那么就可能会出现:子页面Store数据变化后,需要通知到父页面Store进行相应修改的情况。


回顾上一节中,对于Facebook Flux的思考,所遗留的问题点,在Reflux中是否解决了呢?

答案是:肯定的。

这里先简单说明下:

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u012677935/article/details/51917133
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

  • 发表于 2021-05-30 11:10:56
  • 阅读 ( 1012 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢