React Flux 架构使用示例

 2016年04月24日    184     声明


Flux是Facebook官方使用的前端MVC框架,在React的基础上构建。在React 数据流与Flux框架中介绍Flux时,使用了一个实现简单搜索功能的示例,本篇将介绍这个示例的实现过程。


  1. 简介
  2. 创建分发器Dispatcher
  3. 创建Store
  4. 创建视图View
  5. 创建Action
  6. 运行

1. 简介

在这个项目中,我们使用CommonJS规范的Node.js模块系统。所依赖的模块已在package.json中进行管理,如果你对Node.js有民了解,将更容易理解这个示例结构。本示例项目完整代码请查看:flux-demo,项目下载后按以下步骤操作:

  1. npm install,安装所需要模块
  2. npm run build,构建项目
  3. npm startwatchify监视文件修改并实时更新

执行上面步骤1、2后即可点击index.html查看运行效果。运行步骤3可以编写代码的同时实时查看执行效果,在开发过程中建议运行此过程调试。

源码结构

本例中,项目结构如下:

flux-demo
  |
  + css  // 样式文件,本例中bootstrap样式
  + js
    |
    + actions
    + components
    + constants
    + dispatcher
    + stores
    + app.js
  + index.html //示例启动页面


2. 创建分发器Dispatcher

Dispatcher是一个调度中心,Dispatcher只能有一个,负责分发正确的Action路径,并将其转发到已注册的Store处理逻辑中。

本例创建的Dispatcher如下():

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

module.exports = new Dispatcher();

Dispatcher的具体用处,请结合本例的FluxAction.jsFluxStore.js两个文件理解。


3. 创建Store

我们可以使用Node.jsEventEmitter模块来启动一个视图。在本例的controller-views(控制视图)中,通过EventEmitter模块来发送视图'change'事件:

'use strict';
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var FluxConstants = require('../constants/FluxConstants');
var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var _results = {};
var _keyword = '';

// 模似一个搜索源
const _source = [
  {
    name:'IT笔录',
    domain: 'itbilu.com'
  },
  {
    name:'一介布衣',
    domain: 'yijibuyi.com'
  },
  {
    name:'老聂的小站',
    domain: 'niefengjun.cn'
  }
];

/**
 * 实现搜索功能
 * @param  {string} 搜索的内容
 */
function search(text) {
  _keyword = text;
  _source.forEach(function(item){
    if(item.name.indexOf(text)>-1){
      var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
      _results[id] = {
        id: id,
        name:item.name,
        domain: item.domain
      };
    }
  });
}

var FluxStore = assign({}, EventEmitter.prototype, {
  /**
   * 获取Flux 搜索的结果集
   * @return {object}
   */
  getResults: function() {
    return _results;
  },

  /**
   * 获取Flux 搜索的关键字
   * @return {string}
   */
  getKeyword: function() {
    return _keyword;
  },

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

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

// 注册回调处理函数
AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case FluxConstants.FLXU_SEARCH:
      text = action.text.trim();
      if (text !== '') {
        search(text);
        FluxStore.emitChange();
      }
      break;

    default:
      // no op
  }
});

module.exports = FluxStore;

Store用于实现主要的业务逻辑。在本例中,Store实现了监听视图变化、实现搜索功能、注册回调函数等功能。


4. 创建视图View

Flux框架中,视图被分为了两类:controller-view控制视图和其它视图。

4.1 controller-view

在本例中,controller-viewFluxApp.js。它会做两件事司:加载其它子组件、监听'change'事件并传递给子组件:

/**
 * 这个组件是顶层的 "Controller-View"
 * 它会监听 FluxStore 的'change'事件,并将新数据传递给子组件
 */

var React = require('react');
var MainSection = require('./MainSection');
var Footer = require('./Footer');
var FluxStore = require('../stores/FluxStore');

/**
 * 从FluxStore中获取当关的状态
 */
function getFluxState() {
  return {
    results: FluxStore.getResults(),
    keyword: FluxStore.getKeyword()
  };
}

var FluxApp = React.createClass({

  getInitialState: function() {
    return getFluxState();
  },

  componentDidMount: function() {
    FluxStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
    FluxStore.removeChangeListener(this._onChange);
  },

  /**
   * @return {object}
   */
  render: function() {
    return (
      <div>
        <MainSection
          results={this.state.results} 
          keyword={this.state.keyword}
        />
        <Footer />
      </div>
    );
  },

  /**
   * FluxStore的'change'事件处理
   */
  _onChange: function() {
    this.setState(getFluxState());
  }

});

module.exports = FluxApp;

controller-view会在app.js中调用,而app.js是本例入口点(index.html也是引用这个文件编译后的脚本):

/**
 * Flux示例启动文件
 * 
 */

var React = require('react');
var ReactDOM = require('react-dom');

var FluxApp = require('./components/FluxApp');

ReactDOM.render(
  <FluxApp />,
  document.getElementById('fluxapp')
);


4.2 其它View

在本例中,除controller-view还有Footer.jsMainSection.jsResultItem.jsSearchInput.js4个子组件,它们会根据主控视图FluxApp.js在不同情况。

  • Footer.js-显示页面页脚内容
  • MainSection.js-主页面组件
  • ResultItem.js-搜索结果加载组
  • SearchInput.js-搜索输入组件


5. 创建Action

Action用于动作(事件)事件的捕获,同时会接收要传递的数据。本例中,只用到了search一个动作,该方法用于捕获搜索逻辑:

var AppDispatcher = require('../dispatcher/AppDispatcher');
var FluxConstants = require('../constants/FluxConstants');

var FluxAction = {

  /**
   * @param  {string} text
   */
  search: function(text) {
    AppDispatcher.dispatch({
      actionType: FluxConstants.FLXU_SEARCH,
      text: text
    });
  }

};

module.exports = FluxAction;

在上面示例中,引用了AppDispatcher分发器,它会通过dispatch方法以将指定的actionType分发到FluxStore.js中通过AppDispatcher注册的业务处理逻辑。

在上例中,还使用了通过keymirror模块定义的运作常量,如果动作类型较少也可以自己定义actionType常量。


6. 运行

index.html中,引用了以下脚本:

<script src="js/bundle.min.js"></script>

我们并没有上传这个文件,编写完所有功能模块后,可以运行npm run build命令来生成这个文件。该文件由Browserify打包并编译npm模块在浏览器中的运行环境。

命令执行完成后,会生成js/bundle.min.js文件,生成完毕后即可打开index.html查看运行效果。