说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

详解React Flux架构工作方式

HTML文档下载 WORD文档下载 PDF文档下载
Flux是Facebook内部用来构建React应用的一套架构,该架构的最大特点是其倡导的单向数据流方案。本文将重点讨论Facebook Flux架构的工作方式,以及开发者应该如何在项目中使用它。

【编者按】作者Ken Wheeler在《 Getting To Know Flux, the React.js Architecture》一文中 讨论Facebook的Flux架构的工作方式,以及开发者应该如何在项目中使用它。为了让前端开发者更好地掌握本文内容,景庄对该文章进行了翻译,内容如下:

什么是Flux

Flux是Facebook内部用来构建React应用的一套架构。它本身并不是一个框架或库。它仅仅是一个用于完善React应用开发的一种新的应用程序架构,Flux架构最大的特点是其倡导的单向数据流方案。

Facebook还提供了Dispatcher的开源库。你可以将Dispatcher认为是一种全局pub/sub系统的事件处理器,用于向所注册的回调函数广播payloads。通常情况下,如果你使用Flux架构,你可以借助于Facebook提供的这个Dispatcher实现,并且可以借助NodeJS中的EventEmitter模块来配置事件系统, 从而帮助管理应用程序的状态。

组成结构

Flux主要包括下面四个独立的组件,下面简单地解释下:

  • Actions:帮助向Dispatcher传递数据的辅助方法;
  • Dispatcher:接收action,并且向注册的回调函数广播payloads;
  • Stores:应用程序状态的容器&并且含有注册到Dispatcher的回调函数;
  • Controller Views:React组件,从Store获取状态,并将其逐级向下传递给子组件。

让我们通过一幅图来简单看一下这四个部分的关系:


如何处理外部API

但是,很多场景下不仅仅包括应用程序内部的状态数据,还可能包括来自外部的数据,也就是如果我们需要消费RESTful服务,那么我们该如何将这些外部数据引入到Flux架构的单向数据流中呢?经过实践,我们发现,在Action中引入外部数据流是个不错的实践, 数据然后再被送到Store中。

在继续阅读之前,我强烈建议你阅读下Facebook提供的Flux官方示例程序的 源代码。在你简单阅读了这个例子的源代码后,我们再来详细的探讨Flux中这几个组件的作用:

Dispatcher

我们首先来看看Dispatcher的作用:你可以将Dispatcher看成是Flux架构中整个数据流程的管理者。或者你可以将它看成你整个应用的中央交换机。Dispatcher接收actions作为payloads,并且将payloads分发给所注册的回调函数。

那么它就是一个pub/sub吗?

不完全是。Dispatcher会将payload广播给所有向它注册的回调函数,并且包括了允许你以指定次序调用这些回掉函数的执行逻辑,例如在处理前等待数据更新。在Flux架构中只有一个Dispatcher,它是你整个应用的中央Hub。我们来创建一个简单的Dispatcher:

var Dispatcher = require('flux').Dispatcher;var AppDispatcher = new Dispatcher();AppDispatcher.handleViewAction = function(action) {  this.dispatch({    source: 'VIEW_ACTION',    action: action  });}module.exports = AppDispatcher;

在上面的代码中,我们创建了一个Dispatcher的实例,它包括了一个叫做handleViewAction的方法。创建这个方法的目的是为了让你能够轻松的区分, 是视图层触发的action,还是服务器/API触发的action。

在handleViewAction方法中调用了dispatch方法(该方法来自Dispatcher实现),它会将payloads广播给所有注册到它的回调函数 (注意,这里所说的回调是在store中注册给Dispatcher的)。然后,action会触发Store中相应事件,并最终体现在state的更新上。

可以用下面这张图来表示这个过程:


依赖

Facebook所提供的Dispatcher模块还有一个非常赞的功能,那就是它能够定义依赖,并在Store中向Dispatcher注册回调函数。 因此,如果你的应用的某个部分依赖于其他部分的数据更新,为了能够进行合适的渲染,Dispatcher中的waitFor方法将会非常有用。

为了能够利用这一特性,我们需要在Store中存储注册给Dispatcher的回调函数的返回值,这里,我们命名为dispatcherIndex:

ShoeStore.dispatcherIndex = AppDispatcher.register(function(payload) {});

然后在Store中处理每一个被分发的action时,我们可以使用Dispatcher的waitFor方法来确保我们的ShoeStore已经被更新了,示例代码如下:

case 'BUY_SHOES':  AppDispatcher.waitFor([    ShoeStore.dispatcherIndex  ], function() {    CheckoutStore.purchaseShoes(ShoeStore.getSelectedShoes());  });  break;

Stores

在Flux中,可以用Store来分领域的管理应用程序的状态,例如ShoeStore、AppStore等。从一个更高层角度来看,可以简单地认为应用的每一个部分可以对应于一个Store,Store可以用来管理数据,定义数据检索方法,此外,Store中还包括了注册给Dispatcher的回调函数。

下面我们来看一个简单的Store的例子:

var AppDispatcher = require('../dispatcher/AppDispatcher');var ShoeConstants = require('../constants/ShoeConstants');var EventEmitter = require('events').EventEmitter;var merge = require('react/lib/merge');// Internal object of shoesvar _shoes = {};// Method to load shoes from action datafunction loadShoes(data) {  _shoes = data.shoes;}// Merge our store with Node's Event Emittervar ShoeStore = merge(EventEmitter.prototype, {  // Returns all shoes  getShoes: function() {    return _shoes;  },  emitChange: function() {    this.emit('change');  },  addChangeListener: function(callback) {    this.on('change', callback);  },  removeChangeListener: function(callback) {    this.removeListener('change', callback);  }});// Register dispatcher callbackAppDispatcher.register(function(payload) {  var action = payload.action;  var text;  // Define what to do for certain actions  switch(action.actionType) {    case ShoeConstants.LOAD_SHOES:      // Call internal method based upon dispatched action      loadShoes(action.data);      break;    default:      return true;  }    // If action was acted upon, emit change event  ShoeStore.emitChange();  return true;});module.exports = ShoeStore;

以上代码可能做的最重要的一件事就是:我们使用NodeJS中的EventEmitter模块来扩展我们的Store。这使得我们的Store能够监听和广播事件。这也使得我们的视图/组件能够基于这些事件来更新。 这是因为我们的控制器视图(Controller View)会监听我们的Store, 并利用这一点通过发出事件方式通知控制器视图应用程序的状态发生了改变, 从而进行视图层的重新渲染。

在Store中还有一个值得注意的事,那就是我们使用Dispatcher的regiter方法来注册回调函数。 这意味着,我们创建的Store会监听来自AppDispatcher的广播,这里我们使用switch语句来 区分广播内容所对应的action。如果一个相关的action发生了,那么就触发一个change事件,并且监听 此事件的视图会根据新的状态(state)进行重新渲染。这个过程如下图所示。


在上面的代码中,ShoeStore中还包括一个公共方法getShoes(),它可以在控制器视图中被调用, 用于检索所有在_shoes对象中的数据,并且使用这个数据作为组件的状态数据。因为这是个非常简单 的例子,在实际应用开发中你可以使用更加复杂的数据处理逻辑。

Action创建器和Actions

行为创建器(Action Creators)是一组会在视图中被调用的方法(也可以在其他地方使用), 它们用于向Dispatcher发送actions。也就是说,我们使用Dispatcher分发的payloads,实际上就是actions。

在Facebook提供的例子中,action的类型常数被用于定义这些action所被应用的场景,并且是伴随着action一起被传递的。现在,在所注册的回调函数内部,这些actions可以根据他们的action类型来处理。

我们可以看一个简单的类型常数的定义:

var keyMirror = require('react/lib/keyMirror');module.exports = keyMirror({  LOAD_SHOES: null});

在上面的代码中,我们用了React的keyMirror库来生成我们的类型常数的Key/Value对。如果仅仅是看这个文件,我们大致可以猜到我们的应用会加载鞋子(shoes)。借助于类型常数,可以使得应用 中涉及到的事物变得更加地可组织,可以让开发者能通过这样的高层抽象来组织应用程序。

现在,让我们来大致的看一个对应的行为创建器(action creator)的定义:

var AppDispatcher = require('../dispatcher/AppDispatcher');var ShoeStoreConstants = require('../constants/ShoeStoreConstants');var ShoeStoreActions = {  loadShoes: function(data) {    AppDispatcher.handleAction({      actionType: ShoeStoreConstants.LOAD_SHOES,      data: data    })  }};module.exports = ShoeStoreActions;

在上面的代码中,我们在定义了ShoeStoreActions对象,并在其中定义了loadShoes()方法, 该方法内调用了来自AppDispatcher的handleViewAction方法,并且将相应的行为类型与 我们提供的数据相关联。现在,我们可以在我们的视图代码或API中引入这个action文件。我们可以调用ShoeStoreActions.loadShoes(ourData)方法来向Dispatcher发送payload,随后Dispatcher会将它广播出去。ShoeStore则会监听来自Dispatcher的广播,并且触发相应的事件来加载相应的鞋子。

控制器视图 Controller View

你可以简单的将控制器视图理解为React组件,这些组件会监听change事件,如果事件发生了, 就从Stores中获取应用程序新的状态数据。随后,可以将数据通过props逐级向子组件传递。这个过程大致如下图所示:


让我们来看看代码长啥样:

/** @jsx React.DOM */var React = require('react');var ShoesStore = require('../stores/ShoeStore');// Method to retrieve application state from storefunction getAppState() {  return {    shoes: ShoeStore.getShoes()  };}// Create our component classvar ShoeStoreApp = React.createClass({  // Use getAppState method to set initial state  getInitialState: function() {    return getAppState();  },    // Listen for changes  componentDidMount: function() {    ShoeStore.addChangeListener(this._onChange);  },  // Unbind change listener  componentWillUnmount: function() {    ShoesStore.removeChangeListener(this._onChange);  },  render: function() {    return (      <ShoeStore shoes={this.state.shoes} />    );  },    // Update view state when change event is received  _onChange: function() {    this.setState(getAppState());  }});module.exports = ShoeStoreApp;

在上面的代码中,我们使用addChangeListener来监听change事件,并且在接收到事件后更新应用程序的状态数据。

我们的应用程序状态数据存储在Store中,因此我们可以用Store中的公共方法来获取数据,然后将其设置为应用程序的状态。

完整的数据流程

前面我们依次解读了Flux架构中的每一个独立部分,我们有了对Flux架构中的每一个组件的一个大致的认识, 并且能大致的了解了Flux架构的工作流。在这之前,我们已经通过了几张图片描述了数据在组件之间的流向, 现在让我们将上面的子流程组合起来,以更好的理解Flux的数据流:


小结

在读完这篇文章后,如果你之前并不了解Facebook的Flux架构,那么希望你现在能够说理解了。 但另一方面,如果不在React的实际编程开发中使用这一架构,你是很难真正的体会到Flux架构的 特点的。

当你使用Flux后,你会发现没有Flux的React应用开发就相当于是没有jQuery的DOM遍历操作。也就是说, 没有Flux你可以照常进行React应用开发,但是你在代码组织和数据流程上会缺乏优雅的解决方案。

另一个方面,如果你想使用FLux架构,但你并不想使用React,那么你可以参考 Delorean,它是一个Flux框架,并且可以让你使用Ractive.js或者Flight。另一个值得参考的库是 Fluxxor,它实现了在紧耦合的Flux组件中包含一个中央Flux实例。

最后,再次说明,如果要真正的理解Flux,你就得在实际开发中体验它。

原文链接:Getting To Know Flux, the React.js Architecture

译者简介:景庄,前端工程师,关注Node.js、前端工程化。个人博客: http://wwsun.github.com。


欢迎加入CSDN前端交流群2:465281214,进行前端技术交流。  

也可参加CSDN前端大讲堂(微信公开课),享受高含金量在线公开课,与专家讲师在线切磋交流。

如何加入CSDN前端大讲堂?由于该群目前已超过人数限制,所以您首先不得不 扫描下面二维码,加CSDN编辑陈秋歌为好友,然后请她邀请您加入CSDN前端大讲堂微信群。加好友时,请务必注明“申请加入CSDN前端大讲堂”。


通过讲故事的方式来设计优秀网站 “肮脏的”IT工作排行榜 Clank:带有SCSS的高效率移动应用原型框架 欧朋 CTO 罗志宇:谈Sphinx游戏引擎的前世今生 John Carmack大神亲手操刀,Oculus Rift SDK即将发布 从南极之争谈软件架构十个技巧,及成功团队具备的气质 Imation重拳出击,推出新型混合存储平台 张建华:FusionSphere不是简单的OpenStack发行版 Chrome 29来了:地址栏变得更“兼容” Android版增WebRTC 工程师谈在Facebook工作最糟糕的事情 用Scala重构19楼——SDCC 2013讲师王治专访 10款最新且超实用的开发框架 创业公司为什么应该写博客? 向iOS SDK看齐:Facebook Android SDK迎来重大更新 闯向海外:对话AppFlood产品总监陈霄 高手爱跨界:联想智能桌面,27寸大Pad抢先试玩 惠普第三季度收入272亿美元同比下降8% 高管重新布局 原来我们会出这个价钱购买“廉价”iPhone Web设计过程中该做和不该做的 新细节披露:通过电信合作 美国75%网络遭NSA监控 实时Bug检测工具Bugsnag发布API更新 【观察】一大三小:亚马逊,微软、谷歌、IBM ,IaaS四足鼎立 “化云为雨”一小滴:北京,3G手机+蓝牙打印机实现智能抄表 金山云与蓝汛联合提供云基础设施服务 MongoDB出新招,增加对Hive的支持 编程语言收入榜 Java排第三 直接拿来用!10段超有用的Git命令行代码 Google Cast SDK正式版发布:自动适配,修复Bug 频受红杉资本青睐,商业智能初创公司Birst靠的是什么? 2013百度世界大会要点精华回顾 WebApp开发框架Clouda、轻应用... 对新手有用的JavaScript开发小建议 组件如何发布? 一个小问题(请斑竹不要删) 菜鸟提问:怎样才能在编程中把一个数据库生成一个HTML文件! long型能转换为int型么? datagrid中RadioButtonList被自动选择问题? c#中如何比较两个string的大小,如"AB">"AA" return * this 问题 急!!,我VC的一个库文件坏了! 现在真是失恋的季节,各位帮帮我,我是否该该怎么办? 在线等待WIN98操作系统下载的网址 关于模板特殊化的一些问题 三个小问题 那儿有GUN c++ 下载? 关于拨号的问题 学习VB.NET的方向在哪里 两个问题 急!!,我VC的一个库文件坏了! 2000写的程序,到98下运行,发现字体变型变的很厉害,如何解决? 急!!,我VC6的一个库文件坏了! 怎样才能使服务器端更改的数据主动发送的客户端的web上呢 问题,在线等! 关于cdonts的问题,在线等候!!! sos 问一个关联表格操作问题 TNMMSG和TNMMSGServ用于哪些场合?两台电脑之间的通讯能用它吗?是不是不稳定,速度也慢? 那个高手能教教我怎麽用编程实现软盘加密呀??? document.write("<%=server.htmlencode(HTMLstr)%>"); NickTang,thanks,please come in! 非计算机专业所受到的歧视 爲什麽不能重載它的构造函数呢.不知错在哪里? 高手帮我,vb里怎么放动态的.gif格式的图片???!! 请教,程序那里出错误了? 请问如果将Applet嵌入到Html页的<applet>标记换为<object>可以吗?应该怎样来改写? 一颗星星!散分!!! 对客户不负责任的表现 如何解决屏幕分辨率的问题 我的声卡怎么启用不了? 文本编辑器,缺省文件名 初学者请教 让我失望!!! 我的red hat 装上了 windows2000却看不见了? 请大侠帮忙!!(50分) 怎么设可以同时选中StringGrid的多行? 几个问题(高手请进!) 我这个简单,谁都会的 表单取值问题,急! why ???? 小妹问题:为什么安装J2EE后,JCreator无法编译j2ee程序? 高手请进,给分100,急~~ 表单取值问题,急! 请问谁知道人气较旺的共享软件新闻组,要国外的。 如何给给一个CEdit控件发送消息? 人死了会去哪里? 生命该有怎样的意义(我与地坛) 英语翻译事实是我们已经输了这场比赛(表语从句)问题是谁会被派去执行这个任务(表语从句) 人为什么死,死了去哪? 我与地坛第七篇作者对生命的感悟是什么 有什么小孩子可以看的作文题材,最好是可以让孩子有兴趣的题材!看看有没有能类似光盘教学的方式的,可以让孩子一边看一边学的,这样也有兴趣,我在电视上青年学苑频道广育网上看到 人死了会去哪急快错错错常常 我与地坛贯穿了作者对生命怎样的认识 小张把100元人民币存入银行,定期1年,到期获得本金和利息一共103.16元,银行年利率是多少 铝生锈的哪个锈是什么东西? 阿拉伯语Ya Habibiy是什么意思?Ya Habibiy,这句话是阿拉伯语,有懂得阿拉伯语的人吗? 英语翻译急用 cancel 来做哦 左边一个方字,右边一个人字头,人字头下一个生字.是啥字啊? see ya什么意思 the goal of life 今天我们上心理课,老师给我们留作业说:“写20句“我是一个.的人”我想了好久都没几个...中间要填一些比较独特的,不要很简单的,像“善良,可爱.”之类的. 什么都坚持不下来用什么词形容 ? 课本剧里人物的心理想法怎么表现出来?是通过旁白么? Life is kind of a process of self-improving andLife is kind of a process of self-improving and refining.Fighting!To be the best of myself.求翻译 请你选择自己喜欢的成语从成语词典中了解它的典故短点 我是个怎样的孩子作文 Life is a process of feeling这句话中文是什么意思 当人死之后,灵魂他会在哪里? 土地资源开发及利用存在的问题和保护措施 something 人死了到哪里去?人有没有灵魂? 应该如何改变目前我国土地资源利用中存在的这些问题呢? 风雪夜中亮着一盏灯表达了作者怎样的思想感情 请你选择自己喜欢的成语,从成语词典中了解它的典故人教四年级下册课堂作业本里的 我是谁 作文 风雪夜中亮着一盏灯 我在老师窗前下···,表达了我什么的思想感情 招工难 英语怎么说 土地利用存在的问题有哪些 我家对面有一座山,山腰有一所小学.童年我就在这里上学.记得有一年冬天来得特别早,天气格外冷.一个风雪交加的夜晚,我早早就钻进了铺得软软的被窝.一觉醒来,我又习惯地向对面山腰望去, 太空铝是否会生锈 有一篇文章说人死了体重会减轻二十一克,是真的吗 What makes life dreary is the want of motive没有了目的,生活便郁闷无光怎么感觉照字面翻译的不对啊”使生活郁闷的是需要动机“正好译反了 铝生锈怎么处理?摩托车的铝合金轮毂和缸体是铝的,上面有斑点是生锈吗?怎样处理? 鱼加一个则读什么 我是说我们人死之后有没有轮回! 谈谈自己对生命价值的思考 “鱼”加一个“完”字读什么 A的反义词是什么 这上面阿拉伯语是什么意思? 由于糟糕的天气,下午的运动会将被取消.翻译 风雪夜中亮着一盏灯阅读答案我家对面有一座山,山腰有一所小学.童年我就在这里上学.记得有一年冬天来得特别早,天气格外冷.一个风雪交加的夜晚,我早早就钻进了铺得软软的被窝.一觉醒来 周老师是个好老师 (使句子表达的感情更强烈) 实现那样的目标是不可能的.it is impossible to achieve such a goal...英语翻译的对吗 “风雪夜中亮着一盏灯”这个故事的梗概怎么写?我要100字左右的. 英语翻译 谁能翻译一下这个中文是什么?To achieve the first goal in life to fight . 上面竹字头下面一个生 念什么 上面那句阿拉伯语是什么意思呀? 上面是虎字头下面是文是什么读音 有关古代勤奋好学的故事还有许多,请把有关故事的成语或典故写下来少点 我的情侣对戒上的阿拉伯语是什么意思 i find it difficult to achieve this goal中的it是作什么成分.形式宾语吗?i find it is difficult to achieve.这句话是对的吗. 求100个有典故的成语以及解释,不用写成语的故事但要成语解释. 我与地坛中作者对人生进行了长久的思考,内容是什么 人死了以后真的会有灵魂存在吗
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘