从DOM操作看Vue&React的前端组件化,顺带补齐React的demo

前言

接上文:谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo

上次写完博客后,有朋友反应第一内容有点深,看着迷迷糊糊;第二是感觉没什么使用场景,太过业务化,还不如直接写Vue&react的源码分析,我感觉这里有必要说下我的认识。

首先,要写源码分析很难,第一是他本来就很难,所以一般我们是想了解他实现的思路而不是代码;

第二每个开发者有自己发风格,所以你要彻底读懂一个人的代码不容易,除非你是带着当时作者同样的问题不断的寻找解决方案,不断的重构,才可能理解用户的意图。

我们上一次做的事情其实就是根据自己实际的工作经验做了和外面框架类似的事情,虽然代码的健壮、优雅程度跟不上,但却和其它作者一样为解决同样的问题思考得出的方案,上次做的太晚了,后面就草草结束,事实上在我Demo过程中发现一个事实:业务代码都是差不多的,只是一些细节点不一样,所以决定产品质量的依旧是开发者的业务代码能力,框架只是助力而已。

不能了解作者的意图,不能提高本身的编程水平,就算用上了React&Vue这类组件化的框架,也组织不好代码;事实上这类代码因为是面向大型应用的,反而更考验一个人的架构能力,所以大家要多注重内在修养的提升哦。

下面我们进入今天的正题,这里依旧提供一些帮助理解的资料:

github

代码地址:https://github.com/yexiaochai/module/

演示地址:http://yexiaochai.github.io/module/me/index.html

如果对文中的一些代码比较疑惑,可以对比着看看这些文章:

【一次面试】再谈javascript中的继承

【移动前端开发实践】从无到有(统计、请求、MVC、模块化)H5开发须知

【组件化开发】前端进阶篇之如何编写可维护可升级的代码

预览

使用Vue的思考

因为第一个demo是Vue的,React应该也类似,对比之前的代码发现一个重要差异是:

DOM操作真的完全没有了!!!

对,是完全没有DOM操作了,这个是很牛逼的一件事情,因为我觉得有两个地方要摆脱DOM操作很难:

① 我的组件应该放到哪个容器内,我需要一个定位的元素,比如:

1 this.sortModule = new SortModule({
2     view: this,
3     selector: '.js_sort_wrapper',
4     sortEntity: this.sortEntity
5 });

明确的告诉了组件所属的容器

② 我比较疑惑像这类列表类型的事件该如何处理,因为一些必要参数是根据event获取的,比如:

1 listItemClick: function (e) {
2     var el = $(e.currentTarget);
3     //根据el做一些事情
4 }

关于这个Vue的作者认为应该将事件处理程序内联,做显示声明:

你可能注意到这种事件监听的方式违背了传统理念 “separation of concern”。不必担心,
因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护困难。实际上,使用 v-on 有几个好处: 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。 因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何自己清理它们。
<button v-on:click="say('hello!', $event)">Submit</button>
1 methods: {
2   say: function (msg, event) {
3     // 现在我们可以访问原生事件对象
4     event.preventDefault()
5   }
6 }

还有种常用的操作,比如radioList,点击当前选项便选择项目,我们一般的做法是这样的:

1 setIndex: function (i) {
2     this.index = i;
3     this.$('li').removeClass(this.curClass);
4     this.$('li[data-index="' + i + '"]').addClass(this.curClass);
5 }

这样做比较简单,但是会有一个问题,便是数据与dom表现的流程变了,正确的流程是index 变了,dom便根据数据做更新,比如Vue:

1 setIndex: function (i) {
2     this.index = i;
3     //这部分逻辑Vue会自动实现
4     //this.$('li').removeClass(this.curClass);
5     //this.$('li[data-index="' + i + '"]').addClass(this.curClass);
6 }

之前,不考虑性能,我们会直接根据数据重新渲染整个列表,就为一个简单的选中功能,而Vue&React却做到了局部渲染,这个是否牛逼,我相信这个将会是一个核心算法部分,后面有时间一定要深入了解。

根据以上局部解读,我们得到一个结论,只要达成两个条件,就能摆脱DOM操作:

① 知道组件所处容器

② 根据数据渲染页面

PS:我们这里是很简单的一环,没有考虑组件嵌套,组件通信等过于复杂的问题

那么如果达成了以上条件,我们能否做到业务逻辑中不包含dom操作呢?我们下面就来试试。

如何摆脱DOM操作

这里真的是demo类尝试,思维验证,便不使用之前过于复杂的业务逻辑了,这里将me目录拷贝一块出来,依旧以原来的代码做底层依赖,只要列表与顶部排序部分功能,这里为了简化实现,保持代码重用,我们这里直接想将entity模块复用,要求data中的对象必须是一个entity实例,这里第一步是抽象出来了list module模块,于是主控制器变成这样了,事实上这个时候已经没dom操作了:

 1 initEntity: function () {
 2     //实例化排序的导航栏的实体
 3     this.sortEntity = new SortEntity();
 4     this.sortEntity.subscribe(this.renderList, this);
 5 },
 6 
 7 initModule: function () {
 8     //view为注入给组件的根元素
 9     //selector为组件将要显示的容器
10     //sortEntity为注入给组件的数据实体,做通信用
11     //这个module在数据显示后会自动展示
12     this.sortModule = new SortModule({
13         view: this,
14         selector: '.js_sort_wrapper',
15         sortEntity: this.sortEntity
16     });
17     this.listModule = new ListModule({
18         view: this,
19         selector: '.js_list_wrapper',
20         entity: this.sortEntity
21     });
22 },
23 
24 propertys: function ($super) {
25     $super();
26 
27     this.initEntity();
28     this.initModule();
29     this.viewId = 'list';
30     this.template = layoutHtml;
31     this.events = {};
32 }

这里简单看看列表组件的实现,其实就是将原来根View的代码换个位置:

  1 define([
  2     'ModuleView',
  3     'pages/list.data',
  4     'text!pages/tpl.list.html'
  5 
  6 ], function (ModuleView,
  7              listData,
  8              tpl) {
  9     return _.inherit(ModuleView, {
 10 
 11         //此处若是要使用model,处实例化时候一定要保证entity的存在,如果不存在便是业务BUG
 12         initData: function () {
 13 
 14             this.template = tpl;
 15             this.entity.subscribe(this.render, this);
 16 
 17         },
 18 
 19         _timeSort: function (data, sort) {
 20             data = _.sortBy(data, function (item) {
 21                 item = item.from_time.split(':');
 22                 item = item[0] + '.' + item[1];
 23                 item = parseFloat(item);
 24                 return item;
 25             });
 26             if (sort == 'down') data.reverse();
 27             return data;
 28         },
 29 
 30         _sumTimeSort: function (data, sort) {
 31             data = _.sortBy(data, function (item) {
 32                 return parseInt(item.use_time);
 33             });
 34             if (sort == 'down') data.reverse();
 35             return data;
 36         },
 37 
 38         _priceSort: function (data, sort) {
 39             data = _.sortBy(data, function (item) {
 40                 return item.min_price;
 41             });
 42             if (sort == 'down') data.reverse();
 43             return data;
 44         },
 45 
 46         //获取导航栏排序后的数据
 47         getSortData: function (data) {
 48             var tmp = [];
 49             var sort = this.entity.get();
 50 
 51             for (var k in sort) {
 52                 if (sort[k].length > 0) {
 53                     tmp = this['_' + k + 'Sort'](data, sort[k])
 54                     return tmp;
 55                 }
 56             }
 57         },
 58 
 59         //复杂的业务数据处理,为了达到产品的需求,这段代码逻辑与业务相关
 60         //这段数据处理的代码过长(超过50行就过长),应该重构掉
 61         formatData: function (data) {
 62             var item, seat;
 63             var typeMap = {
 64                 'g': 'g',
 65                 'd': 'd',
 66                 't': 't',
 67                 'c': 'g'
 68             };
 69 
 70             //出发时间对应的分钟数
 71             var fromMinute = 0;
 72 
 73             //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
 74             var d = 1464192000000;
 75             var date = new Date();
 76             var now = parseInt(date.getTime() / 1000);
 77             date.setTime(d);
 78             var year = date.getFullYear();
 79             var month = date.getMonth();
 80             var day = date.getDate();
 81             var toBegin;
 82             var seatName, seatIndex, iii;
 83 
 84             //处理坐席问题,仅显示二等座,一等座,特等座 无座
 85             //                二等座 一等座 商务座 无座 动卧 特等座
 86             var my_seats = {};
 87             var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座'];
 88 
 89             for (var i = 0, len = data.length; i < len; i++) {
 90                 fromMinute = data[i].from_time.split(':');
 91                 fromMinute[0] = fromMinute[0] + '';
 92                 fromMinute[1] = fromMinute[1] + '';
 93                 if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
 94                 if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
 95                 date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
 96                 fromMinute = parseInt(date.getTime() / 1000)
 97                 toBegin = parseInt((fromMinute - now) / 60);
 98 
 99                 data[i].toBegin = toBegin;
100 
101                 //处理车次类型问题
102                 data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other';
103 
104                 seat = data[i].seats;
105                 //所有余票
106                 data[i].sum_ticket = 0;
107                 //最低价
108                 data[i].min_price = null;
109 
110                 for (var j = 0, len1 = seat.length; j < len1; j++) {
111                     if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
112                     data[i].sum_ticket += parseInt(seat[j].seat_yupiao);
113 
114                     //坐席问题如果坐席不包括上中下则去掉
115                     seatName = seat[j].seat_name;
116                     //去掉上中下
117                     seatName = seatName.replace(/上|中|下/g, '');
118                     if (!my_seats[seatName]) {
119                         my_seats[seatName] = parseInt(seat[j].seat_yupiao);
120                     } else {
121                         my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
122                     }
123                 }
124                 //这里myseat为对象,需要转换为数组
125                 //将定制坐席转为排序后的数组
126                 data[i].my_seats = [];
127                 for (iii = 0; iii < seatSort.length; iii++) {
128                     if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
129                         name: seatSort[iii],
130                         yupiao: my_seats[seatSort[iii]]
131                     });
132                 }
133 
134                 my_seats = {};
135             }
136 
137             return data;
138         },
139 
140         //完成所有的筛选条件,逻辑比较重
141         getViewModel: function () {
142             var data = this.formatData(listData);
143             data = this.getSortData(data);
144             return {data: data};
145         }
146 
147     });
148 
149 });
View Code

就这种简单的改变,貌似便摆脱了DOM操作,页面所有的状态事实上是可以做到由数据控制的,但是这里没有形成“标签化”,似乎不太好,于是我们来试试是否能改造为标签化的代码。

我们这里的业务代码(module与entity)没有什么需要改动的,这里主要在底层做改造,这里在我看来是提供了一种“语法糖”的东西,这里的具体概念后续阅读Vue源码再深入了解,这里先照着做,这里看结果想实现,也是我们常用的一种设计方案,首先我们的index编程了这个样子:

1 <article class="cm-page page-list" id="main">
2     <div class="js_sort_wrapper sort-bar-wrapper">
3         <mySortBar :entity="sortEntity"></mySortBar>
4     </div>
5     <myList :entity="listEntity" :sort="sort"></myList>
6 </article>
 1 (function () {
 2     require.config({
 3         paths: {
 4             'text': 'libs/require.text',
 5 
 6             'AbstractView': 'js/view',
 7             'AbstractEntity': 'js/entity',
 8             'ModuleView': 'js/module'
 9         }
10     });
11 
12     require(['pages/list.label'], function (List) {
13         var list = new List();
14         list.show();
15     });
16 })();

PS:里面的js钩子基本无用了

这里标签化带来的好处是,根View中有一段实例代码可以不用与选择器映射了,比如这个:

1 this.sortModule = new SortModule({
2     //view: this,
3     //selector: '.js_sort_wrapper',
4     //sortEntity: this.sortEntity
5 });

因为处于组件中,其中所处位置已经定了,view实例或者entity实例全部是跟View显示注入的,这里根View中参考Vue的使用,新增一个$components与$entities属性,然后增加一$watch对象。

大家写底层框架时,私有属性或者方法使用_method的方式,如果是要释放的可以是$method这种,一定要“特殊化”防止被实例或者继承覆盖
 1 define([
 2     'AbstractView', 'pages/en.sort', 'pages/mod.sort', 'pages/mod.list'
 3 ], function (AbstractView, SortEntity, SortModule, ListModule) {
 4     return _.inherit(AbstractView, {
 5         propertys: function ($super) {
 6             $super();
 7             this.$entities = {
 8                 sortEntity: SortEntity
 9             };
10             this.$components = {
11                 mySortBar: SortModule,
12                 listModule: ListModule
13             };
14             this.$watch = {
15 
16             };
17             this.viewId = 'list';
18             this.template = layoutHtml;
19             this.events = {};
20         }
21     });
22 });

他这种做法,需要组件在显示后框架底层将刚刚的业务代码实现,使用组件生成的html代码将原来标签的占位符给替换掉。

这里在组件也需要明示根View需要注入什么给自己:

PS:事实上这个可以不写,写了对后续属性的计算有好处

//记录需要根View注入的属性
props:[sortEntity],

PS:底层什么时候执行替换这个是有一定时机的,我们这里暂时放到根View展示后,这里更好的实现,后续我们在Vue与React中去找寻

因为我们这里是demo类实现,为降低难度,我们为每一个组件动态增加一个div包裹层,于是,我们在跟View中,在View展示后,我们另外多加一段逻辑:

1 //实例化实体,后面要用
2 this._initEntity();
3 //新增标签逻辑
4 this._initComponent();

然后将实体与组件的实例化放到框架底层,这里实体的实例化比较简单(如果有特殊数据需求再说,这里只考虑最简单情况):

1 _initEntity: function() {
2     var key, entities = this.$entities;
3     //这里没有做特殊化,需要注意
4     for(key in entities) {
5         this[key] = new entities[key]();
6     }
7 },

而实例化组件的工作复杂许多,因为他需要将页面中的自定义标签替换掉,还需要完成很多属性注入操作:

1 _initComponent: function() {
2     var key, components = this.$components;
3     for(key in components) {
4         //这里实例化的过程有点复杂,首先将页面的标签做一个替换
5         var s = ''
6     }
7 },
 1 _initComponent: function() {
 2     var key, components = this.$components;
 3     var el, attributes, attr, param, clazz, i, len, tmp, id, name;
 4 
 5     //这里实例化的过程有点复杂,首先将页面的标签做一个替换
 6     for(key in components) {
 7         param = {};
 8         clazz = components[key];
 9         //由原型链上获取根元素要注入给子组件的属性(这个实现好像不太好)
10         attributes = clazz.prototype.props;
11 
12         //首先获取标签dom元素,因为html是不区分大小写的,这里将标签小写
13         el = this.$(key.toLowerCase());
14         if(!el[0]) continue;
15 
16         if(attributes) {
17             for (i = 0, len = attributes.length; i < len; i++) {
18                 attr = attributes[i];
19                 name = el.attr(':' + attr);
20                 param[attr] = this[name] || name;
21             }
22         }
23 
24         //创建一个空div去替换原来的标签
25         id = _.uniqueId('componenent-id-');
26         tmp = $('<div component-name="' + key + '" id="' + id + '"></div>');
27         tmp.insertBefore(el);
28         el.remove();
29         param.selector = '#' + id;
30         param.view = this;
31         this[key] = new components[key](param);
32     }
33 
34 },

于是这个标签便能正常展示了:

1 <article class="cm-page page-list" id="main">
2     <div class="js_sort_wrapper sort-bar-wrapper">
3         <mySortBar :entity="sortEntity" :myname="111"></mySortBar>
4     </div>
5     <myList :entity="sortEntity" :sort="sort"></myList>
6 </article>

后面想要把这段代码去掉也十分轻易,我这里就不进行了:

1 'click .js_sort_item li ': function (e) {
2     var el = $(e.currentTarget);
3     var sort = el.attr('data-sort');
4     this.entity['set' + sort]();
5 }

总结

这里首先根据上次Vue的demo产生了一些思考,并且以简单的demo验证了这些思考,楼主在使用过程中发现Vue很多好的点子,后续应该会深入研究,并且以实际项目入手,这里回到今天的正题,我们使用React实现上次遗留的demo。

React的实现

在我最初接触React的时候,React Native还没出现,所以很多人对React的关注不高,当时做移动端直接放弃了angular,以体量来说,React也不在我们的考虑范围内,谁知道野心勃勃的Facebook搞出了React Native,让React彻底的跟着火了一把,事实上只要有能力以JavaScript统一Native UI的公司,这个实现就算换个框架依旧会火,虽然都已经这么火了,但是React的文档却不怎样,我后续也有试水React Native的兴趣,届时再与各位分享。

PS:根据之前的反馈,这次demo稍微做简单点,也只包含顶部导航和列表即可:

  1 <!doctype html>
  2 <html>
  3 <head>
  4     <meta charset="UTF-8">
  5     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui"/>
  6     <meta content="yes" name="apple-mobile-web-app-capable"/>
  7     <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
  8     <meta name="format-detection" content="telephone=no"/>
  9     <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
 10     <link href="./pages/list.css" rel="stylesheet" type="text/css"/>
 11     <title>组件化</title>
 12 </head>
 13 <body>
 14 <div class="cm-header">
 15     <h1 class="cm-page-title js_title"> 组件化Demo </h1>
 16 </div>
 17 
 18 <article class="cm-page page-list" id="main">
 19 </article>
 20 
 21 
 22 <script src="./libs/react-with-addons.js"></script>
 23 <script src="./libs/JSXTransformer.js"></script>
 24 <script type="text/javascript" src="./pages/list.data.js"></script>
 25 <script type="text/javascript" src="./libs/underscore.js"></script>
 26 <script type="text/jsx">
 27 
 28 
 29 
 30 var MySortBar = React.createClass({
 31     getInitialState: function() {
 32         return {
 33             time: 'up',
 34             sumTime: '',
 35             price: ''
 36         };
 37     },
 38 
 39     resetData: function () {
 40         this.setState({
 41             time: '',
 42             sumTime: '',
 43             price: ''
 44         });
 45     },
 46 
 47     setTime: function () {
 48         this._setData('time');
 49     },
 50 
 51     setSumTime: function () {
 52         this._setData('sumTime');
 53     },
 54 
 55     setPrice: function () {
 56         this._setData('price');
 57     },
 58 
 59     _setData: function (key) {
 60         var param = {};
 61 
 62         //如果设置当前key存在,则反置,否则清空筛选,设置默认值
 63         if (this.state[key] != '') {
 64             if (this.state[key] == 'up') param[key] = 'down';
 65             else param[key] = 'up';
 66         } else {
 67             this.resetData();
 68             param[key] = 'down';
 69         }
 70         this.setState(param);
 71     },
 72 
 73     _getClassName: function(icon) {
 74         return 'icon-sort ' + icon;
 75     },
 76 
 77     render: function () {
 78         return (
 79             <ul className="bus-tabs sort-bar js_sort_item">
 80                 <li className="tabs-item" onClick={this.setTime}  >出发时间<i className={this._getClassName(this.state.time)}></i></li>
 81                 <li className="tabs-item" onClick={this.setSumTime} >耗时<i className={this._getClassName(this.state.sumTime)}></i></li>
 82                 <li className="tabs-item"  onClick={this.setPrice} >价格<i className={this._getClassName(this.state.price)}></i></li>
 83             </ul>
 84         );
 85     }
 86 
 87 });
 88 
 89 var Seat = React.createClass({
 90     render: function () {
 91         var seat = this.props.seat;
 92 
 93         return (
 94             <span >{seat.name}({seat.yupiao }) </span>
 95         );
 96     }
 97 });
 98 
 99 var Item = React.createClass({
100     render: function () {
101 
102         var item = this.props.item;
103         var mapping = {
104             'g': '高速',
105             't': '特快',
106             'd': '高速动车',
107             'c': '城际高铁',
108             'z': '直达'
109         };
110 
111         var seats = item.my_seats.map(function(item){
112             return <Seat seat={item}/>;
113         });
114 
115 
116         return (
117             <li className="bus-list-item ">
118                 <div className="bus-seat">
119                     <span className=" fl">{item.train_number } | {mapping[item.my_train_number] || '其它'} </span>
120                     <span className=" fr">{parseInt(item.use_time / 60) + '小时' + item.use_time % 60 + ''}</span>
121                     </div>
122                     <div className="detail">
123                     <div className="sub-list set-out">
124                     <span className="bus-go-off">{item.from_time}</span> <span className="start"><span className="icon-circle s-icon1">
125                     </span>{item.from_station }</span> <span className="fr price">¥{item.min_price}起</span>
126                 </div>
127                 <div className="sub-list">
128                         <span className="bus-arrival-time">{item.to_time}</span> <span className="end"><span className="icon-circle s-icon2">
129                         </span>{item.to_station}</span> <span className="fr ">{item.sum_ticket}张</span>
130                 </div>
131                 </div>
132                 <div className="bus-seats-info" >
133                     {seats}
134                 </div>
135             </li>
136         );
137     }
138 });
139 
140 
141 var MyList = React.createClass({
142 
143     formatData: function (data) {
144 
145         var item, seat;
146         var typeMap = {
147             'g': 'g',
148             'd': 'd',
149             't': 't',
150             'c': 'g'
151         };
152 
153         //出发时间对应的分钟数
154         var fromMinute = 0;
155 
156         //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
157         var d = 1464192000000;
158         var date = new Date();
159         var now = parseInt(date.getTime() / 1000);
160         date.setTime(d);
161         var year = date.getFullYear();
162         var month = date.getMonth();
163         var day = date.getDate();
164         var toBegin;
165         var seatName, seatIndex, iii;
166 
167         //处理坐席问题,仅显示二等座,一等座,特等座 无座
168         //                二等座 一等座 商务座 无座 动卧 特等座
169         var my_seats = {};
170         var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座'];
171 
172         for (var i = 0, len = data.length; i < len; i++) {
173             fromMinute = data[i].from_time.split(':');
174             fromMinute[0] = fromMinute[0] + '';
175             fromMinute[1] = fromMinute[1] + '';
176             if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
177             if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
178             date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
179             fromMinute = parseInt(date.getTime() / 1000)
180             toBegin = parseInt((fromMinute - now) / 60);
181 
182             data[i].toBegin = toBegin;
183 
184             //处理车次类型问题
185             data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other';
186 
187             seat = data[i].seats;
188             //所有余票
189             data[i].sum_ticket = 0;
190             //最低价
191             data[i].min_price = null;
192 
193             for (var j = 0, len1 = seat.length; j < len1; j++) {
194                 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
195                 data[i].sum_ticket += parseInt(seat[j].seat_yupiao);
196 
197                 //坐席问题如果坐席不包括上中下则去掉
198                 seatName = seat[j].seat_name;
199                 //去掉上中下
200                 seatName = seatName.replace(/上|中|下/g, '');
201                 if (!my_seats[seatName]) {
202                     my_seats[seatName] = parseInt(seat[j].seat_yupiao);
203                 } else {
204                     my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
205                 }
206             }
207             //这里myseat为对象,需要转换为数组
208             //将定制坐席转为排序后的数组
209             data[i].my_seats = [];
210             for (iii = 0; iii < seatSort.length; iii++) {
211                 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
212                     name: seatSort[iii],
213                     yupiao: my_seats[seatSort[iii]]
214                 });
215             }
216 
217             my_seats = {};
218         }
219 
220         return data;
221     },
222 
223     render: function () {
224 
225         var main;
226         var data = this.formatData(this.props.data);
227 
228 
229         main = data.map(function(item) {
230             return <Item item={item}/>;
231         });
232 
233         return (
234             <ul className="bus-list js_bus_list ">
235                 {main}
236             </ul>
237         );
238     }
239 
240 });
241 
242 var data = getListData();
243 
244 React.render(
245     <div>
246         <div className="js_sort_wrapper sort-bar-wrapper">
247             <MySortBar />
248         </div>
249         <MyList data={data} />
250     </div>,
251     document.getElementById('main')
252 );
253 
254 </script>
255 
256 
257 </body>
258 </html>
View Code

他这个语法据说是让开发变得更简单了,我反正是不喜欢,这里有个不好的地方,之前数据实体全部是在根View上实例化的,然后注入给子View,React这里属性完全独享了,现在我触发了状态的改变,如何通知到list组件重新渲染排序呢?

React 组件通信

这里React子组件之间如何通信暂没有研究出来,所以将需要通信的数据做到了父组件中
  1 <!doctype html>
  2 <html>
  3 <head>
  4     <meta charset="UTF-8">
  5     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui"/>
  6     <meta content="yes" name="apple-mobile-web-app-capable"/>
  7     <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
  8     <meta name="format-detection" content="telephone=no"/>
  9     <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
 10     <link href="./pages/list.css" rel="stylesheet" type="text/css"/>
 11     <title>组件化</title>
 12 </head>
 13 <body>
 14 <div class="cm-header">
 15     <h1 class="cm-page-title js_title"> 组件化Demo </h1>
 16 </div>
 17 
 18 <article class="cm-page page-list" id="main">
 19 </article>
 20 
 21 
 22 <script src="./libs/react-with-addons.js"></script>
 23 <script src="./libs/JSXTransformer.js"></script>
 24 <script type="text/javascript" src="./pages/list.data.js"></script>
 25 <script type="text/javascript" src="./libs/underscore.js"></script>
 26 <script type="text/jsx">
 27 
 28 
 29 
 30 var MySortBar = React.createClass({
 31     _getClassName: function(icon) {
 32         return 'icon-sort ' + icon;
 33     },
 34 
 35     render: function () {
 36         var state = this.props.state;
 37         return (
 38             <ul className="bus-tabs sort-bar js_sort_item">
 39                 <li className="tabs-item" onClick={this.props.setTime}  >出发时间<i className={this._getClassName(state.time)}></i></li>
 40                 <li className="tabs-item" onClick={this.props.setSumTime} >耗时<i className={this._getClassName(state.sumTime)}></i></li>
 41                 <li className="tabs-item"  onClick={this.props.setPrice} >价格<i className={this._getClassName(state.price)}></i></li>
 42             </ul>
 43         );
 44     }
 45 
 46 });
 47 
 48 var Seat = React.createClass({
 49     render: function () {
 50         var seat = this.props.seat;
 51 
 52         return (
 53             <span >{seat.name}({seat.yupiao }) </span>
 54         );
 55     }
 56 });
 57 
 58 var Item = React.createClass({
 59     render: function () {
 60 
 61         var item = this.props.item;
 62         var mapping = {
 63             'g': '高速',
 64             't': '特快',
 65             'd': '高速动车',
 66             'c': '城际高铁',
 67             'z': '直达'
 68         };
 69 
 70         var seats = item.my_seats.map(function(item){
 71             return <Seat seat={item}/>;
 72         });
 73 
 74 
 75         return (
 76             <li className="bus-list-item ">
 77                 <div className="bus-seat">
 78                     <span className=" fl">{item.train_number } | {mapping[item.my_train_number] || '其它'} </span>
 79                     <span className=" fr">{parseInt(item.use_time / 60) + '小时' + item.use_time % 60 + ''}</span>
 80                     </div>
 81                     <div className="detail">
 82                     <div className="sub-list set-out">
 83                     <span className="bus-go-off">{item.from_time}</span> <span className="start"><span className="icon-circle s-icon1">
 84                     </span>{item.from_station }</span> <span className="fr price">¥{item.min_price}起</span>
 85                 </div>
 86                 <div className="sub-list">
 87                         <span className="bus-arrival-time">{item.to_time}</span> <span className="end"><span className="icon-circle s-icon2">
 88                         </span>{item.to_station}</span> <span className="fr ">{item.sum_ticket}张</span>
 89                 </div>
 90                 </div>
 91                 <div className="bus-seats-info" >
 92                     {seats}
 93                 </div>
 94             </li>
 95         );
 96     }
 97 });
 98 
 99 var MyList = React.createClass({
100 
101     formatData: function (data) {
102 
103         var item, seat;
104         var typeMap = {
105             'g': 'g',
106             'd': 'd',
107             't': 't',
108             'c': 'g'
109         };
110 
111         //出发时间对应的分钟数
112         var fromMinute = 0;
113 
114         //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
115         var d = 1464192000000;
116         var date = new Date();
117         var now = parseInt(date.getTime() / 1000);
118         date.setTime(d);
119         var year = date.getFullYear();
120         var month = date.getMonth();
121         var day = date.getDate();
122         var toBegin;
123         var seatName, seatIndex, iii;
124 
125         //处理坐席问题,仅显示二等座,一等座,特等座 无座
126         //                二等座 一等座 商务座 无座 动卧 特等座
127         var my_seats = {};
128         var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座'];
129 
130         for (var i = 0, len = data.length; i < len; i++) {
131             fromMinute = data[i].from_time.split(':');
132             fromMinute[0] = fromMinute[0] + '';
133             fromMinute[1] = fromMinute[1] + '';
134             if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
135             if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
136             date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
137             fromMinute = parseInt(date.getTime() / 1000)
138             toBegin = parseInt((fromMinute - now) / 60);
139 
140             data[i].toBegin = toBegin;
141 
142             //处理车次类型问题
143             data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other';
144 
145             seat = data[i].seats;
146             //所有余票
147             data[i].sum_ticket = 0;
148             //最低价
149             data[i].min_price = null;
150 
151             for (var j = 0, len1 = seat.length; j < len1; j++) {
152                 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
153                 data[i].sum_ticket += parseInt(seat[j].seat_yupiao);
154 
155                 //坐席问题如果坐席不包括上中下则去掉
156                 seatName = seat[j].seat_name;
157                 //去掉上中下
158                 seatName = seatName.replace(/上|中|下/g, '');
159                 if (!my_seats[seatName]) {
160                     my_seats[seatName] = parseInt(seat[j].seat_yupiao);
161                 } else {
162                     my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
163                 }
164             }
165             //这里myseat为对象,需要转换为数组
166             //将定制坐席转为排序后的数组
167             data[i].my_seats = [];
168             for (iii = 0; iii < seatSort.length; iii++) {
169                 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
170                     name: seatSort[iii],
171                     yupiao: my_seats[seatSort[iii]]
172                 });
173             }
174 
175             my_seats = {};
176         }
177 
178         return data;
179     },
180 
181     _timeSort: function (data, sort) {
182         data = _.sortBy(data, function (item) {
183             item = item.from_time.split(':');
184             item = item[0] + '.' + item[1];
185             item = parseFloat(item);
186             return item;
187         });
188         if (sort == 'down') data.reverse();
189         return data;
190     },
191 
192     _sumTimeSort: function (data, sort) {
193         data = _.sortBy(data, function (item) {
194             return parseInt(item.use_time);
195         });
196         if (sort == 'down') data.reverse();
197         return data;
198     },
199 
200     _priceSort: function (data, sort) {
201         data = _.sortBy(data, function (item) {
202             return item.min_price;
203         });
204         if (sort == 'down') data.reverse();
205         return data;
206     },
207 
208     //获取导航栏排序后的数据
209     getSortData: function (data) {
210         var tmp = [];
211         var sort = this.props.state;
212 
213         for (var k in sort) {
214             if (sort[k].length > 0) {
215                 tmp = this['_' + k + 'Sort'](data, sort[k])
216                 return tmp;
217             }
218         }
219     },
220 
221     render: function () {
222 
223         var main;
224         var data = this.formatData(this.props.data);
225         data = this.getSortData(data);
226 
227         main = data.map(function(item) {
228             return <Item item={item}/>;
229         });
230 
231         return (
232             <ul className="bus-list js_bus_list ">
233                 {main}
234             </ul>
235         );
236     }
237 
238 });
239 
240 var App = React.createClass({
241     getInitialState: function() {
242         return {
243             time: 'up',
244             sumTime: '',
245             price: ''
246         };
247     },
248 
249     resetData: function () {
250         this.setState({
251             time: '',
252             sumTime: '',
253             price: ''
254         });
255     },
256 
257     setTime: function () {
258         this._setData('time');
259     },
260 
261     setSumTime: function () {
262         this._setData('sumTime');
263     },
264 
265     setPrice: function () {
266         this._setData('price');
267     },
268 
269     _setData: function (key) {
270         var param = {};
271 
272         //如果设置当前key存在,则反置,否则清空筛选,设置默认值
273         if (this.state[key] != '') {
274             if (this.state[key] == 'up') param[key] = 'down';
275             else param[key] = 'up';
276         } else {
277             this.resetData();
278             param[key] = 'down';
279         }
280         this.setState(param);
281     },
282 
283     formatData: function (data) {
284 
285         var item, seat;
286         var typeMap = {
287             'g': 'g',
288             'd': 'd',
289             't': 't',
290             'c': 'g'
291         };
292 
293         //出发时间对应的分钟数
294         var fromMinute = 0;
295 
296         //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
297         var d = 1464192000000;
298         var date = new Date();
299         var now = parseInt(date.getTime() / 1000);
300         date.setTime(d);
301         var year = date.getFullYear();
302         var month = date.getMonth();
303         var day = date.getDate();
304         var toBegin;
305         var seatName, seatIndex, iii;
306 
307         //处理坐席问题,仅显示二等座,一等座,特等座 无座
308         //                二等座 一等座 商务座 无座 动卧 特等座
309         var my_seats = {};
310         var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座'];
311 
312         for (var i = 0, len = data.length; i < len; i++) {
313             fromMinute = data[i].from_time.split(':');
314             fromMinute[0] = fromMinute[0] + '';
315             fromMinute[1] = fromMinute[1] + '';
316             if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
317             if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
318             date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
319             fromMinute = parseInt(date.getTime() / 1000)
320             toBegin = parseInt((fromMinute - now) / 60);
321 
322             data[i].toBegin = toBegin;
323 
324             //处理车次类型问题
325             data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other';
326 
327             seat = data[i].seats;
328             //所有余票
329             data[i].sum_ticket = 0;
330             //最低价
331             data[i].min_price = null;
332 
333             for (var j = 0, len1 = seat.length; j < len1; j++) {
334                 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
335                 data[i].sum_ticket += parseInt(seat[j].seat_yupiao);
336 
337                 //坐席问题如果坐席不包括上中下则去掉
338                 seatName = seat[j].seat_name;
339                 //去掉上中下
340                 seatName = seatName.replace(/上|中|下/g, '');
341                 if (!my_seats[seatName]) {
342                     my_seats[seatName] = parseInt(seat[j].seat_yupiao);
343                 } else {
344                     my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
345                 }
346             }
347             //这里myseat为对象,需要转换为数组
348             //将定制坐席转为排序后的数组
349             data[i].my_seats = [];
350             for (iii = 0; iii < seatSort.length; iii++) {
351                 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
352                     name: seatSort[iii],
353                     yupiao: my_seats[seatSort[iii]]
354                 });
355             }
356 
357             my_seats = {};
358         }
359 
360         return data;
361     },
362 
363     render: function () {
364 
365         var main;
366         var data = this.formatData(this.props.data);
367         main = data.map(function(item) {
368             return <Item item={item}/>;
369         });
370 
371         return (
372             <div>
373                 <div className="js_sort_wrapper sort-bar-wrapper">
374                 <MySortBar state={this.state} setTime={this.setTime} setSumTime={this.setSumTime} setPrice={this.setPrice}/>
375                 </div>
376                 <MyList data={data} state={this.state} />
377             </div>
378         );
379     }
380 
381 });
382 
383 var data = getListData();
384 
385 React.render(
386     <App data={data}/>,
387     document.getElementById('main')
388 );
389 
390 </script>
391 
392 
393 </body>
394 </html>
View Code

总结

react的中文文档整理较差,很多资料找不到,jsx语法比较怪异,不是所有人能接受,我去找模板循环时候压根就没找到,所以jsx有个特点,他让你不得不去拆分你的组件,在我写React代码中,感觉React代码控制力度要重一点,但是如果没有良好的架构能力,我可以毫不夸张的说,你依旧写不好业务代码。

至于React与Vue的优劣,这个方面见仁见智吧,好了今天的文章到此为止。

后续我们可能会深入分析下Vue的实现,在React Native上做深入,有兴趣的同学可以持续关注。

文章有任何不足错误,请您提出,因为小钗也是第二次使用React写demo,如果使用不当请多包涵

posted on 2016-05-23 01:34  叶小钗  阅读(4966)  评论(5编辑  收藏  举报