[译]利用flux来构建reactjs项目

原文地址

首先我不想浪费大家的时间在flux的细节上,关于它的详情,可以点击Facebook flux site,我想告诉大家的是,利用fluxreact的结合可以更好的处理业务交互以及视图同步.

一切源于状态

在同一个页面上,状态可以理解为它上面的一个checkbox,随着checkbox显中或者不显中,状态都会不一样,不用去关心状态改变时要做什么,而是去关心状态改变跟UI交互的方式.

一个小故事

我现在在构建一个大型的电话交换机的web项目,它起始于我在Marcello的工作,那时候像backboneangularjs都没出现,为了实现在浏览器上面显示每个电话交换机用户的调用,系统是以事件为基础,意为着没有保存每次调用的状态,但是可以从每个调用的服务端状态改变那里得到独立的事件.

长话短说,依赖这些事件来改变dom,每个事件类型对应一个dom里的操作,目前来看这似乎不是一个坏主意,但是每个调用事件有20-30个状态,比如播号,挂断等,这些慢慢让项目扩展与维护失去控制,而且问题也越来越多.

后来我重构了整个项目,那时候backbone还不错,所以就用它来代替之前的单一事件框架,每次调用就传递完整的状态,从服务端传递的调用状态对应到集合中的模型,然后传递到视图中去,每当集合改变的时候,就会重新渲染视图来同步状态,这时已经没什么问题了.

关于这个故事有两点需要注意下:

  • 渲染之前拥有完整的状态要比在数据位上构建状态要容易的多
  • 渲染是一个不错的概念,虽然还有些问题,不过非常容易来处理应用程序逻辑

下面来看看不同框架对状态改变跟UI的交互方式,其中的输写规范统一是cjs(commonjs)规范,后续可以利用browserify来部署

Backbone

  • main.js 应用核心JS

var UserModel = require('./UserModel.js');
var CheckboxView = require('./CheckboxView.js');
new CheckboxView({model: new UserModel()}).render().$el.appendTo('body');
  • UserModel.js 模型js,存储状态信息

var model = Backbone.Model.extend({
  defaults: {
    notify: false
  }
});
  • CheckboxView.js 视图信息,模型的交互对象

var View = Backbone.View.extend({
  events: {
    'click input': 'updateUser',
    template: handlebars.compile('<input type="checkbox" {#if notify}checked{/if}/> Notify'),
    initialize: function () {
      this.listenTo(this.model, 'change', this.render);
    },
    render: function () {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    },
    updateUser: function () {
      this.model.set('notify', this.$el.find('input').is(':checked'));
    }
  }
});

上面的脚本传递一个模型给定义好的视图,视图利用handlebars来解析模板,初始化的时候添加对模型的监听,功能就是当模型改变的时候可以渲染视图,所以当触发视图里的dom事件时,利用监听关系也可以同步视图,从而达到模型状态的改变与UI视图的同步.

优点

backbone让视图更新变的非常容易,任何UI的交互都可以更新模型,而模型更新又可以影响视图,从而脱离利用原生dom操作来更新UI,一切是以模型来做为主角

缺点

backbone渲染的时候都是更新整个视图,这并不是一个好的功能,因为dom渲染更多的节点是需要花费很多时间的,而且整个视图渲染有可能影响交互,比如当在输入框里输入东西的时候.

这里也有一个扩展方面的问题,给视图更多修改模型的能力并不是一个好的主意,有可能改变某个状态会影响到应用程序其它部分.有时候需要销毁绑定在模型上的多个监听,其中每个监听用来处理不同的应用程序状态,这个也会潜在的存在管理问题.

Angularjs

  • main.js 应用核心JS

angular.module('myApp', [])
.factory('UserService', function () {
  var user = {};
  return {
    getUser: function () {
      return user;
    }
  }
})
.controller('MyCtrl', function ($scope, UserService) {
  $scope.user = UserService.getUser();
});
  • index.html 应用程序首页中的一部分

<body ng-app="myApp">
  <div ng-controller="MyCtrl">
    <input type="checkbox" ng-model="user.notify"/>
  </div>
</body>

上面的代码运用了ng里的双向绑定功能,这是一个强大的功能,通过注入UserService解决了模型的功能,仅仅在视图里bind一个状态就可以实现双向同步,看起来代码非常漂亮.

优点

ng里的双向绑定极大了减少了代码量,而且能够提高开发效率,个人非常喜欢它里面的原型继承.对于模型思想的实现弱化了,不仅可以包含基本的状态属性,而且也可以添加各种不同类型的行为.

缺点

即使我们写再少的代码也会碰到跟backbone相同的问题,而且更糟.在大型应用程序中,多个组件想了解属性的改变,虽然双向绑定非常强大,但是仍然没有设置属性的地方,只能在内部改变模型,你将只能通过事件或者手动监听属性来了解属性的改变,这就是跟backbone相同的问题,应用程序的多个部分想了解某个属性的状态改变.

ng的双向绑定依赖于脏值检测,这个贯穿整个ng上下文中,而且很难辨认出它是否处于脏值检测,而且这个过程也会影响应用程序的性能,当手动的调用$apply的时候也有可能产生异常.

Flux

flux并不是一个框架,只是一个利用reactjs来作为自己控制器与视图的一个架构,这里没有mvc, mv*,不用去关心这是mv什么,只需要了解它是一个容易理解而且扩展性极好的架构就行了,就算要跟mv系列比较的话,那么它算是mv*,关于构建flux的时候,有三个概念需要了解下:

  • Dispatcher(调度)
  • Stores(存储)
  • Components(组件)

虽然这三个概念没有跟上面的例子进行关联,但是它们之间有相似的地方,模型相当于flux里的Stores,在backboneangularjs中,视图改变依赖于模型,不过在flux里不是这样的

flux视图中是不会更新Stores的,而是发送一个动作,然后Dispatcher通知Stores接收这个动作,所以重点是Stores持有所有应用程序的状态,当一个动作接收到时,就可以产生一个服务端请求等.所以视图是利用Dispatcher来传递动作即而来更新Stores的.

Stores更新它的状态之后,持有改变监听事件的视图就会重新渲染,这里的原理跟backbone一样,只是跟它的区别在于,一个组件不会更新它所有的视图内容,只会更新一部分.这是靠reactjs里的virtual DOM来实现的,这实在是不错.

所以整个流程是这样的, DISPATCHER -> STORES -> COMPONENTS,如果一个组件想改变状态,则必须要给DISPATCHER发送一个动作,而在典型的MVC中,通常是这样的,MODEL <-> CONTROLLER <-> VIEW,状态改变是双向的,所以想想model,controller,view都是双向的,相互作用的,这是造成问题的关键所在.在flux中,不用关心你的应用程序有多复杂,流程都是这样的,这才是它让项目变的简单的地方.

我们来看看它的代码例子

在下面的例子中会用到一个reactjs扩展,它包含自己的dispatcher,store,更多关于它的详情,请点击flux-react

  • main.js 应用主要js文件

/** @jsx React.DOM */
var React = require('flux-react');
var Checkbox = require('./Checkbox.js');
React.renderComponent(<Checkbox/>, document.body);
  • UserStore.js 存储js,类似于模型

var React = require('flux-react');
var user = {
  notify: false
};
module.exports = React.createStore({
  getNotify: function () {
    return user.notify;
  },

  // dispatch runs whenever a new action is received
  // from the dispatcher
  dispatch: function (payload) {
    switch (payload.type) {
      case 'CHANGE_NOTIFY':
        user.notify = payload.notify;
        this.flush(); // Give notice that changes has been done
        break;
    }
  }
});
  • Checkbox.js 组件js,关联store与dispatcher

/** @jsx React.DOM */
var React = require('flux-react');
var UserStore = require('./UserStore.js');
module.exports = React.createClass({

  // What stores the component is dependant of
  stores: [UserStore],
  getInitialState: function () {
    return {
      notify: UserStore.getNotify()
    };
  },

  // A react-flux callback that triggers when any
  // of its dependant stores updates (flushes)
  storesDidUpdate: function () {
    this.setState({
      notify: UserStore.getNotify()
    });
  },
  notify: function () {
    React.dispatch({
      type: 'CHANGE_NOTIFY',
      notify: this.refs.checkbox.getDOMNode().checked
    });
  },
  render: function () {
    return (
      <input ref="checkbox" type="checkbox" checked={this.state.notify} onChange={this.notify}/>
    )
  }
});

嗯,看起来代码有点多,试着这样想想,flux上面的例子里,假如需求改变了,有可能我们要移除很多代码,但是在flux的例子里,应用程序添加任何东西,都不需要移除任何代码,如果你需要一个新的store,只要添加它然后给依赖它的组件就可以,如果需要更多的视图,只需要在创建它并添加到使用它的组件中去,而且也不会影响当前环境内的其它视图与模型.

reactjsflux目前还比较新,还需要在大型应用程序里去检验,不过我相信这个概念非常吸引人.希望这篇文章能够帮助你开始利用reactjsflux来构建应用程序.最后感谢大家的宝贵意见!

参考资料

时间: 2024-08-22 20:28:29

[译]利用flux来构建reactjs项目的相关文章

利用browserify与requirejs构建ng项目

随着业务的增长,利用ng来构建项目时候,文件数量就会显著的上升,从而上线部署的时候就要考虑压缩合并问题,随着前端工程化的发展,现在已经有很多种第三方工具来实现开发与部署的便捷性,browserify与requirejs就是其中的两个比较好多的工具. browserify以commonjs模块开发规范来约束前端模块开发,最后上线时提供命令行生成合并文件,详情请 点击这里 requirejs以amd模块开发规范来约束前端模块开发,最后上线的时候提供r.js命令行工具来生成合并压缩文件,详情请 点击这

[译] 如何使用 JavaScript 构建响应式引擎 —— Part 2:计算属性和依赖追踪

本文讲的是[译] 如何使用 JavaScript 构建响应式引擎 -- Part 2:计算属性和依赖追踪, 原文地址:How to build a reactive engine in JavaScript. Part 2: Computed properties and dependency tracking 原文作者:本文已获原作者 Damian Dulisz 授权 译文出自:掘金翻译计划 译者:IridescentMia 校对者:malcolmyu,AceLeeWinnie Hey!如果你

利用微服务构建现代应用(二)

本文讲的是利用微服务构建现代应用(二),[编者的话]本文是如何用微服构建现代应用的第二部分,介绍了如何用MongoDB中实现微服务,在迁移到微服务之前需要考虑的问题以及使用MongoDB构建微服务架构的客户案例. 在上一篇博文中,我介绍了微服务的背景知识及其优势.本文将介绍如何使用MongoDB构建微服务以及在实施微服务的项目中应该要注意的问题. MongoDB如何实现微服务 企业要享受到微服务的好处需要一些基本的技术原则,其中最主要的是灵活的数据模型.冗余.自动化和可扩展性. 灵活的数据模型:

[译] 如何理智地构建复杂用户界面

本文讲的是[译] 如何理智地构建复杂用户界面, 原文地址:How to build complex user interfaces without going completely insane 原文作者:Illia Kolodiazhnyi 译文出自:掘金翻译计划 译者:Changkun Ou 校对者:MuYunyun, noturnot 如何理智地构建复杂用户界面 我最近在构建一个复杂.动态的 Web 应用的用户界面(UI).在这条路上,我学到了一些宝贵的经验教训. 下面的这些技巧是我希望有

qt-CMake 构建Qt项目,出现无法找到合适的Qt版本

问题描述 CMake 构建Qt项目,出现无法找到合适的Qt版本 途中错误如何解决,求大神给出解决方案!尽量详细些,初学CMake.谢谢了! 解决方案 你去QT官网下载对应的版本,它都自带Cmake等

[译] 如何使用 JavaScript 构建响应式引擎 —— Part 1:可观察的对象

本文讲的是[译] 如何使用 JavaScript 构建响应式引擎 -- Part 1:可观察的对象, 原文地址:How to build a reactive engine in JavaScript. Part 1: Observable objects 原文作者:本文已获原作者 Damian Dulisz 授权 译文出自:掘金翻译计划 译者:IridescentMia 校对者:reid3290,malcolmyu 响应式的方式 随着对强健.可交互的网站界面的需求不断增多,很多开发者开始拥抱响

maven 构建web项目时war名称由什么决定

使用maven 构建web项目时,war包的名称由什么决定呢? 默认的war名称是构件ID加上版本号,例如: shop_goods-0.0.1-SNAPSHOT.war或exam4-0.0.1-SNAPSHOT.war  有哪些方式可以指定war包的名称呢? 方式一: 使用插件maven-war-plugin,pom配置如下:  Xml代码   <plugin>                   <groupId>org.apache.maven.plugins</grou

利用微服务构建现代应用(一)

本文讲的是利用微服务构建现代应用(一),[编者的话]本文介绍了微服务如何消除传统的整体化软件架构存在的问题,微服务跟SOA的关系,微服务所利用的新技术如容器.编排框架等,以及使用微服务带来的好处. 本文是关于微服务的两篇博文中的第一篇.这篇博文介绍了微服务的背景知识,微服务所使用的新技术以及使用微服务带来的好处. 简介 随着互联网公司在高度竞争性的市场中需要快速灵活的复制其开发环境,应用程序的开发正变得越来越复杂.庞大并且整体的应用程序在过去是企业的竞争力,但是在新的情境中却使得快速部署新的服务

滴滴快的精打细算:利用大数据构建产业生态圈

ZDNet至顶网软件频道消息: 随着2014年初那场旷日持久补贴大战的落幕,"土豪"一词也随之成为人们对滴滴快的的印象标签.殊不知,在疯狂补贴的同时,滴滴快的也有一颗精打细算的"心"--利用大数据构建更广阔的产业生态圈. 其实从2012年开始,滴滴快的便迅速网罗了360个城市中近两亿"打车族".每天600多万订单生成,每个小时,数十万订单数据汇入滴滴快的后台.通过对这些人们出行数据的汇总.分析,滴滴快的构建出一套用户画像系统,由此而形成一个全新的