|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +category : JavaScript |
| 4 | +tagline: "" |
| 5 | +tags : [react,flux] |
| 6 | +--- |
| 7 | +{% include JB/setup %} |
| 8 | + |
| 9 | +<a href="http://christianalfoni.github.io/javascript/2014/08/20/react-js-and-flux.html" target="_blank">原文地址</a> |
| 10 | + |
| 11 | +首先我不想浪费大家的时间在`flux`的细节上,关于它的详情,可以点击<a href="http://facebook.github.io/flux/" target="_blank">Facebook flux site</a>,我想告诉大家的是,利用`flux`与`react`的结合可以更好的处理业务交互以及视图同步. |
| 12 | + |
| 13 | +### 一切源于状态 |
| 14 | + |
| 15 | +在同一个页面上,状态可以理解为它上面的一个`checkbox`,随着`checkbox`显中或者不显中,状态都会不一样,不用去关心状态改变时要做什么,而是去关心状态改变跟UI交互的方式. |
| 16 | + |
| 17 | + |
| 18 | +### 一个小故事 |
| 19 | + |
| 20 | +我现在在构建一个大型的电话交换机的web项目,它起始于我在<a href="http://www.marcello.no/" target="_blank">Marcello</a>的工作,那时候像`backbone`与`angularjs`都没出现,为了实现在浏览器上面显示每个电话交换机用户的调用,系统是以事件为基础,意为着没有保存每次调用的状态,但是可以从每个调用的服务端状态改变那里得到独立的事件. |
| 21 | + |
| 22 | +长话短说,依赖这些事件来改变dom,每个事件类型对应一个dom里的操作,目前来看这似乎不是一个坏主意,但是每个调用事件有20-30个状态,比如播号,挂断等,这些慢慢让项目扩展与维护失去控制,而且问题也越来越多. |
| 23 | + |
| 24 | +后来我重构了整个项目,那时候`backbone`还不错,所以就用它来代替之前的单一事件框架,每次调用就传递完整的状态,从服务端传递的调用状态对应到集合中的模型,然后传递到视图中去,每当集合改变的时候,就会重新渲染视图来同步状态,这时已经没什么问题了. |
| 25 | + |
| 26 | +关于这个故事有两点需要注意下: |
| 27 | + |
| 28 | +* 渲染之前拥有完整的状态要比在数据位上构建状态要容易的多 |
| 29 | + |
| 30 | +* 渲染是一个不错的概念,虽然还有些问题,不过非常容易来处理应用程序逻辑 |
| 31 | + |
| 32 | +下面来看看不同框架对状态改变跟UI的交互方式,其实的输写规范统一是`cjs`(commonjs)规范,后续可以利用`browserify`来部署 |
| 33 | + |
| 34 | + |
| 35 | +### Backbone |
| 36 | + |
| 37 | +* main.js 应用核心JS |
| 38 | + |
| 39 | +```js |
| 40 | + |
| 41 | +var UserModel = require('./UserModel.js'); |
| 42 | +var CheckboxView = require('./CheckboxView.js'); |
| 43 | +new CheckboxView({model: new UserModel()}).render().$el.appendTo('body'); |
| 44 | + |
| 45 | +``` |
| 46 | + |
| 47 | +* UserModel.js 模型js,存储状态信息 |
| 48 | + |
| 49 | +```js |
| 50 | + |
| 51 | +var model = Backbone.Model.extend({ |
| 52 | + defaults: { |
| 53 | + notify: false |
| 54 | + } |
| 55 | +}); |
| 56 | + |
| 57 | +``` |
| 58 | + |
| 59 | +* CheckboxView.js 视图信息,模型的交互对象 |
| 60 | + |
| 61 | +```js |
| 62 | + |
| 63 | +var View = Backbone.View.extend({ |
| 64 | + events: { |
| 65 | + 'click input': 'updateUser', |
| 66 | + template: handlebars.compile('<input type="checkbox" {#if notify}checked{/if}/> Notify'), |
| 67 | + initialize: function () { |
| 68 | + this.listenTo(this.model, 'change', this.render); |
| 69 | + }, |
| 70 | + render: function () { |
| 71 | + this.$el.html(this.template(this.model.toJSON())); |
| 72 | + return this; |
| 73 | + }, |
| 74 | + updateUser: function () { |
| 75 | + this.model.set('notify', this.$el.find('input').is(':checked')); |
| 76 | + } |
| 77 | + } |
| 78 | +}); |
| 79 | + |
| 80 | +``` |
| 81 | + |
| 82 | +上面的脚本传递一个模型给定义好的视图,视图利用`handlebars`来解析模板,初始化的时候添加对模型的监听,功能就是当模型改变的时候可以渲染视图,所以当触发视图里的dom事件时,利用监听关系也可以同步视图,从而达到模型状态的改变与UI视图的同步. |
| 83 | + |
| 84 | +### 优点 |
| 85 | + |
| 86 | +`backbone`让视图更新变的非常容易,任务UI的交互都可以更新模型,而模型更新又可以影响视图,从而脱离利用原生`dom`操作来更新UI,一切是以模型来做为主角 |
| 87 | + |
| 88 | +### 缺点 |
| 89 | + |
| 90 | +`backbone`渲染的时候都是更新整个视图,这并不是一个好的功能,因为`dom`渲染更多的节点是需要花费很多时间的,而且整个视图渲染有可能影响交互,比如当在输入框里输入东西的时候. |
| 91 | + |
| 92 | +这里也有一个扩展方面的问题,给视图更多修改模型的能力并不是一个好的主意,有可能改变某个状态会影响到应用程序其它部分.有时候需要销毁绑定在模型上的多个监听,其中每个监听用来处理不同的应用程序状态,这个也会潜在的存在管理问题. |
| 93 | + |
| 94 | +### Angularjs |
| 95 | + |
| 96 | +* main.js 应用核心JS |
| 97 | + |
| 98 | +```js |
| 99 | + |
| 100 | +angular.module('myApp', []) |
| 101 | +.factory('UserService', function () { |
| 102 | + var user = {}; |
| 103 | + return { |
| 104 | + getUser: function () { |
| 105 | + return user; |
| 106 | + } |
| 107 | + } |
| 108 | +}) |
| 109 | +.controller('MyCtrl', function ($scope, UserService) { |
| 110 | + $scope.user = UserService.getUser(); |
| 111 | +}); |
| 112 | + |
| 113 | +``` |
| 114 | + |
| 115 | +* index.html 应用程序首页中的一部分 |
| 116 | + |
| 117 | +```html |
| 118 | + |
| 119 | +<body ng-app="myApp"> |
| 120 | + <div ng-controller="MyCtrl"> |
| 121 | + <input type="checkbox" ng-model="user.notify"/> |
| 122 | + </div> |
| 123 | +</body> |
| 124 | + |
| 125 | +``` |
| 126 | +上面的代码运用了`ng`里的双向绑定功能,这是一个强大的功能,通过注入`UserService`解决了模型的功能,仅仅在视图里`bind`一个状态就可以实现双向同步,看起来代码非常漂亮. |
| 127 | + |
| 128 | +### 优点 |
| 129 | + |
| 130 | +`ng`里的双向绑定极大了减少了代码量,而且能够提高开发效率,个人非常喜欢它里面的原型继承.对于模型思想的实现弱化了,不仅可以包含基本的状态属性,而且也可以添加各种不同类型的行为. |
| 131 | + |
| 132 | +### 缺点 |
| 133 | + |
| 134 | +即使我们写再少的代码也会碰到跟`backbone`相同的问题,而且更糟.在大型应用程序中,多个组件想了解属性的改变,虽然双向绑定非常强大,但是仍然没有设置属性的地方,只能在内部改变模型,你将只能通过事件或者手动监听属性来了解属性的改变,这就是跟`backbone`相同的问题,应用程序的多个部分想了解某个属性的状态改变. |
| 135 | + |
| 136 | +`ng`的双向绑定依赖于`脏值检测`,这个贯穿整个`ng`上下文中,而且很难辨认出它是否处于`脏值检测`,而且这个过程也会影响应用程序的性能,当手动的调用`$apply`的时候也有可能产生异常. |
| 137 | + |
| 138 | +### Flux |
| 139 | + |
| 140 | +`flux`并不是一个框架,只是一个利用`reactjs`来作为自己控制器与视图的一个架构,这里没有`mvc, mv*`,不用去关心这是`mv`什么,只需要了解它是一个容易理解而且扩展性极好的架构就行了,就算要跟`mv`系列比较的话,那么它算是`mv*`,关于构建`flux`的时候,有三个概念需要了解下: |
| 141 | + |
| 142 | +* Dispatcher(调度) |
| 143 | + |
| 144 | +* Stores(存储) |
| 145 | + |
| 146 | +* Components(组件) |
| 147 | + |
| 148 | +虽然这三个概念没有跟上面的例子进行关联,但是它们之间有相似的地方,模型相当于`flux`里的`Stores`,在`backbone`与`angularjs`中,视图改变依赖于模型,不过在`flux`里不是这样的 |
| 149 | + |
| 150 | +在`flux`视图中是不会更新`Stores`的,而是发送一个动作,然后`Dispatcher`通知`Stores`接收这个动作,所以重点是`Stores`持有所有应用程序的状态,当一个动作接收到时,就可以产生一个服务端请求等.所以视图是利用`Dispatcher`来传递动作即而来更新`Stores`的. |
| 151 | + |
| 152 | +当`Stores`更新它的状态之后,持有改变监听事件的视图就会重新渲染,这里的原理跟`backbone`一样,只是跟它的区别在于,一个组件不会更新它所有的视图内容,只会更新一部分.这是靠`reactjs`里的`virtual DOM`来实现的,这实在是不错. |
| 153 | + |
| 154 | +所以整个流程是这样的, `DISPATCHER -> STORES -> COMPONENTS`,如果一个组件想改变状态,则必须要给`DISPATCHER`发送一个动作,而在典型的`MVC`中,通常是这样的,`MODEL <-> CONTROLLER <-> VIEW`,状态改变是双向的,所以想想`model`,`controller`,`view`都是双向的,相互作用的,这是造成问题的关键所在.在`flux`中,不用关心你的应用程序有多复杂,流程都是这样的,这才是它让项目变的简单的地方. |
| 155 | + |
| 156 | +我们来看看它的代码例子 |
| 157 | + |
| 158 | +在下面的例子中会用到一个`reactjs`扩展,它包含自己的`dispatcher`,`store`,更多关于它的详情,请点击<a href="https://github.com/christianalfoni/flux-react" target='_blank'>flux-react</a> |
| 159 | + |
| 160 | +* main.js 应用主要js文件 |
| 161 | + |
| 162 | +```js |
| 163 | + |
| 164 | +/** @jsx React.DOM */ |
| 165 | +var React = require('flux-react'); |
| 166 | +var Checkbox = require('./Checkbox.js'); |
| 167 | +React.renderComponent(<Checkbox/>, document.body); |
| 168 | + |
| 169 | +``` |
| 170 | + |
| 171 | +* UserStore.js 存储js,类似于模型 |
| 172 | + |
| 173 | +```js |
| 174 | + |
| 175 | +var React = require('flux-react'); |
| 176 | +var user = { |
| 177 | + notify: false |
| 178 | +}; |
| 179 | +module.exports = React.createStore({ |
| 180 | + getNotify: function () { |
| 181 | + return user.notify; |
| 182 | + }, |
| 183 | + |
| 184 | + // dispatch runs whenever a new action is received |
| 185 | + // from the dispatcher |
| 186 | + dispatch: function (payload) { |
| 187 | + switch (payload.type) { |
| 188 | + case 'CHANGE_NOTIFY': |
| 189 | + user.notify = payload.notify; |
| 190 | + this.flush(); // Give notice that changes has been done |
| 191 | + break; |
| 192 | + } |
| 193 | + } |
| 194 | +}); |
| 195 | + |
| 196 | +``` |
| 197 | + |
| 198 | +* Checkbox.js 组件js,关联store与dispatcher |
| 199 | + |
| 200 | +```js |
| 201 | + |
| 202 | +/** @jsx React.DOM */ |
| 203 | +var React = require('flux-react'); |
| 204 | +var UserStore = require('./UserStore.js'); |
| 205 | +module.exports = React.createClass({ |
| 206 | + |
| 207 | + // What stores the component is dependant of |
| 208 | + stores: [UserStore], |
| 209 | + getInitialState: function () { |
| 210 | + return { |
| 211 | + notify: UserStore.getNotify() |
| 212 | + }; |
| 213 | + }, |
| 214 | + |
| 215 | + // A react-flux callback that triggers when any |
| 216 | + // of its dependant stores updates (flushes) |
| 217 | + storesDidUpdate: function () { |
| 218 | + this.setState({ |
| 219 | + notify: UserStore.getNotify() |
| 220 | + }); |
| 221 | + }, |
| 222 | + notify: function () { |
| 223 | + React.dispatch({ |
| 224 | + type: 'CHANGE_NOTIFY', |
| 225 | + notify: this.refs.checkbox.getDOMNode().checked |
| 226 | + }); |
| 227 | + }, |
| 228 | + render: function () { |
| 229 | + return ( |
| 230 | + <input ref="checkbox" type="checkbox" checked={this.state.notify} onChange={this.notify}/> |
| 231 | + ) |
| 232 | + } |
| 233 | +}); |
| 234 | + |
| 235 | +``` |
| 236 | + |
| 237 | +嗯,看起来代码有点多,试着这样想想,`flux`上面的例子里,假如需求改变了,有可能我们要移除很多代码,但是在`flux`的例子里,应用程序添加任务东西,都不需要移除任务代码,如果你需要一个新的`store`,只要添加它然后给依赖它的组件就可以,如果需要更多的视图,只需要在创建它并添加到使用它的组件中去,而且也不会影响当前环境内的`其它视图与模型`. |
| 238 | + |
| 239 | +`reactjs`与`flux`目前还比较新,还需要在大型应用程序里去检验,不过我相信这个概念非常吸引人.希望这篇文章能够帮助你开始利用`reactjs`和`flux`来构建应用程序.最后感谢大家的宝贵意见! |
| 240 | + |
| 241 | + |
| 242 | +### 参考资料 |
| 243 | + |
| 244 | +* <a href="https://github.com/christianalfoni/flux-react" target="_blank">官方github</a> |
| 245 | + |
| 246 | +* <a href="http://facebook.github.io/flux/docs/todo-list.html#content" target="_blank">官方Todo例子</a> |
| 247 | + |
| 248 | +* <a href="https://github.com/christianalfoni/flux-react-boilerplate" target="_blank">一个flux-react脚手架项目</a> |
| 249 | + |
| 250 | + |
| 251 | + |
0 commit comments