简谈flux

Flux

 

耍了解 Redux,首先要从 Flux 说起,可以认为 Redux 是 Flux 思想的另 一 种实现方 式,通过了解 Flux,我们可以知道 Flux一族框架(其中就包括 Redux)贯彻的最重要的 观点一一单向数据流,更重要的是,我们可以发现 Flux框架的缺点,从而深刻地认识到 Redux 相对于 Flux 的改进之处 。让我们来看看 Flux 的历史,实际上, Flux是和React 同时面世的,在 2013 年, Face­book公司让 React亮相的同时,也推出了 Flux框架, React和 Flux相辅相成, Facebook认为两者结合在一起才能构建大型的 JavaScript应用 。做 一 个容易理解的对比, React是用来替换 jQuery 的,那么 Flux 就是以替换Backbone.js、 Ember.js 等 MVC 一族框架为目的 。

在 MVC ( Model-View-Controller)的世界里, React相当于 v (也就是 View)的部分,只涉及页面的渲染一旦涉及应用的数据管理部分,还是交给 Model 和 Controller,不过,Flux并不是 一 个 MVC 框架,事实上, Flux认为 MVC 框架存在很大问题,它推翻了MVC 框架, 并用一个新的思维来管理数据流转 。

首先先说一下什么是mvc框架

MVC 框架是业界广泛接受的一种前端应用框架类型,这种框架把应用分为三个部分:

model(模型) 负责管理数据,大部分业务逻辑也应该放在model中

view(视图)负责渲染用户界面,应该避免view中涉及业务逻辑

controller(控制器)负责接受用户的输入,根据用户的输入调用对应的model部分逻辑,把产生的数据结果交给view部分,让view渲染出必要的输出

关系图如下

对于 MVC 框架,为了让数据流可控, Controller应该是中心,当 View 要传递消息 给 Model 时,应该调用 Controller 的方法,同样,当 Model 要更新 View 时,也应该通过 Controller 引发新的渲染 。

当Facebook推出Flux时,招致了很多质疑。 很多人都说, Flux只不过是一个对数 据流管理更加严格的 MVC 框架而已 。 这种说法不完全准确,但是 一定意义上也说明了 Flux 的一个特点:更严格的数据流控制 。

Facebook 已经无心在 MVC 框架上纠缠,他们用 Flux 框架来代替原有的 MVC 框架,他们提出的 Flux 框架大致结构是图 3-3 的模样 。 

 

一个 Flux 应用包含四个部分,我们先粗略了解一下:

Dispatcher,处理动作分发,维持 Store 之间的依赖关系; 

Store,负责存储数据和处理数据相关逻辑 ;

Action,驱动 Dispatcher 的 JavaScript 对象

View,视图部分,负责显示用户界面。 

如果非要把 Flux 和 MVC 做一个结构对比,那么, Flux 的 Dispatcher相当于 MVC 的 Controller, Flux 的 Store相当于 MVC 的 Model, Flux 的 View 当然就对应 MVC 的 View 了,至于多出来的这个 Action,可以理解为对应给 MVC 框架的用户请求 。

在 MVC 框架中,系统能够提供什么样的服务,通过 Controller暴露函数来实现 。 每增加 一个功能, Controller往往就要增加一个函数;在 Flux 的世界里,新增加功能 并不需要 Dispatcher增加新的函数,实际上, Dispatcher 自始至终只需要暴露一个函数 Dispatch, 当需要增加新的功能时,要做的是增加一种新的 Action类型, Dispatcher 的对 外接口并不用改变 。

当需要扩充应用所能处理的“请求”时, MVC 方法就需要增加新的 Controller,而 对于 Flux 则只是增加新的 Action。 

1. Dispatcher

首先,我们要创造一个 Dispatcher,几乎所有应用都只需要拥有 一 个 Dispatcher, 对于我们这个简单的应用更不例外 。 在 src/AppDispatcher. 中,我们创造这个唯 一 的 Dispatcher 对象,代码如下: 

import {Dispatcher} from 'flux';

export default new Dispatcher();

非常简单,我们引人 flux库中的 Dispatcher类,然后创造一个新的对象作为这个文 件的默认输出就足够了 。 在其他代码中,将会引用这个全局唯一的 Dispatcher对象。

Dispatcher存在的作用,就是用来派发 action,接下来我们就来定义应用中涉及的 action。 

 

2. action

ction顾名思义代表一个“动作”,不过这个动作只是一个普通的 JavaScript对象,代

表一个动作的纯数据,类似于 DOMAPI 中的事件( event)。 甚至,和事件相比, action 其实还是更加纯粹的数据对象,因为事件往往还包含 一些方法,比如点击事件就有 preventDefault方法,但是 action对象不自带方法,就是纯粹的数据。

作为管理, action 对象必须有一个名为 type 的字段,代表这个 action 对象的类型,

为了记录日志和 debug 方便,这个 type 应该是字符串类型 。
定义 action 通常需要两个文件,一个定义 action 的类型,一个定义 action 的构造函

数(也称为 action creator)。 分成两个文件的主要原因是在 Store 中会根据 action 类型做 不同操作,也就有单独导人 action类型的需要 。

在 src/ActionTypes.js 中,我们定义 action 的类型,代码如下:

export const INCREMENT = 'increment';

export const DECREMENT = 'decrement';

  

在这个例子中,用户只能做两个动作,一个是点击“+”按钮, 一个是点击 钮,所以我们只有两个 action类型则CREMENT 和 DECREMENT。

现在我们在 src/Actions. 文件中定义 action 构造函数:

import * as ActionTypes from './ActionTypes.js';
import AppDispatcher from './AppDispatcher.js';

export const increment = (counterCaption) => {
  AppDispatcher.dispatch({
    type: ActionTypes.INCREMENT,
    counterCaption: counterCaption
  });
};

export const decrement = (counterCaption) => {
  AppDispatcher.dispatch({
    type: ActionTypes.DECREMENT,
    counterCaption: counterCaption
  });
};

  

虽然出于业界习惯,这个文件被命名为 Actions.js,但是要注意里面定义的并不是action对象本身,而是能够产生并派发 action对象的函数。

在 Actions. 文件中,引入了 ActionTypes 和 AppDispatcher, 看得出来是要直接使用Dispatcher。

这个 Actions. 导出了两个 action构造函数 increment和 decrement,当这两个函数被调用的时候,创造了对应的 action对象,并立即通过 AppDispatcher.dispatch 函数派发出去 。 派发出去的 action对象最后怎么样了呢?在下面关于 Store 的部分可以看到 。

 

3. Store

一个 Store也是一个对象,这个对象存储应用状态,同时还要接受 Dispatcher派发的 动作,根据动作来决定是否要更新应用状态 。

接下来我们创造 Store相关的代码,因为使用 Flux之后代码文件数量会增多,再把 所有源代码文件都放在 src 目录下就不容易管理了 。 所以我们在 src 下创建一个子目录 stores,在这个子目录里面放置所有的 Store代码。

在前面demo的 Contro!Panel应用例子里,有三个 Counter组件,还有一个统计三个 Counter 计数值之和的功能,我们遇到的麻烦就是这两者之间的状态如何同步的问题,现在,我们创造两个 Store,一个是为 Counter组件服务的 CounterStore,另 一个就是为总 数服务的 SummaryStore。

我们首先添加 CounterStore,放在 src/stores/Count巳rStore. 文件中 。

const counterValues = {
  'First': 0,
  'Second': 10,
  'Third': 30
};


const CounterStore = Object.assign({}, EventEmitter.prototype, {
  getCounterValues: function() {
    return counterValues;
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }

});

  

当 Store 的状态发生变化的时候, 需要通知应用的其他部分做必要的响应 。 在我们 的应用中,做出响应的部分当然就是 View部分,但是我们不应该硬编码这种联系,应 该用消息的方式建立 Store 和 View 的联系 。 这就是为什么我们让 CounterStore 扩展了 EventEmitter.prototype,等于让 CounterStore成了 EventEmitter对象, 一个 EventEmitter 实例对象支持下列相关函数 。

1,emit 函数,可以广播一个特定事件,第一个参数是字符串类型的事件名称 ;

2,on 函数,可以增加一个挂在这个 EventEmitter对象特定事件上的处理函数,第一个参数是字符串类型 的事件名称,第二个参数是处理函数;

3,removeListener 函数, 和 on 函数做的 事情相反, 删 除挂在这个 EventEmitter对象特定事件上的处理函数,和 on 函数一样, 第一个参数是事件名称 ,第二个参数 是处理函数。 要注意, 如果要调用 removeListener函数, 就一定要保留对处理函 数的引用。

对于 CounterStore对象, emitChange、 addChangeListener和 removeChangeListener函 数就是利用 EventEmitter上述的三个函数完成对 CounterStore状态更新的广播 、添加监昕 函数和删 除监昕 函数等操作 。

CounterStore 函数还提供一个 getCounterValues 函数,用于让应用中其他模块可以读 取当前的计数值,当前的计数值存储在文件模块级的变量 counterValues 中

上面实现的 Store 只有注册到 Dispatcher实例上才能真正发挥作用,所以还需要增加 下列代码:

import AppDispatcher from '../AppDispatcher.js';
CounterStore.dispatchToken = AppDispatcher.register((action) => {
  if (action.type === ActionTypes.INCREMENT) {
    counterValues[action.counterCaption] ++;
    CounterStore.emitChange();
  } else if (action.type === ActionTypes.DECREMENT) {
    counterValues[action.counterCaption] --;
    CounterStore.emitChange();
  }
});

  

posted on 2018-05-22 15:18  南果梨  阅读(233)  评论(0编辑  收藏  举报