前言

接上文:谈谈我对前端组件化中“组件”的理解,顺带写个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操作很难:

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

 this.sortModule = new SortModule({
view: this,
selector: '.js_sort_wrapper',
sortEntity: this.sortEntity
});

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

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

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

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

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

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

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

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

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

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

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

① 知道组件所处容器

② 根据数据渲染页面

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

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

如何摆脱DOM操作

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

 initEntity: function () {
//实例化排序的导航栏的实体
this.sortEntity = new SortEntity();
this.sortEntity.subscribe(this.renderList, this);
}, initModule: function () {
//view为注入给组件的根元素
//selector为组件将要显示的容器
//sortEntity为注入给组件的数据实体,做通信用
//这个module在数据显示后会自动展示
this.sortModule = new SortModule({
view: this,
selector: '.js_sort_wrapper',
sortEntity: this.sortEntity
});
this.listModule = new ListModule({
view: this,
selector: '.js_list_wrapper',
entity: this.sortEntity
});
}, propertys: function ($super) {
$super(); this.initEntity();
this.initModule();
this.viewId = 'list';
this.template = layoutHtml;
this.events = {};
}

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

 define([
'ModuleView',
'pages/list.data',
'text!pages/tpl.list.html' ], function (ModuleView,
listData,
tpl) {
return _.inherit(ModuleView, { //此处若是要使用model,处实例化时候一定要保证entity的存在,如果不存在便是业务BUG
initData: function () { this.template = tpl;
this.entity.subscribe(this.render, this); }, _timeSort: function (data, sort) {
data = _.sortBy(data, function (item) {
item = item.from_time.split(':');
item = item[0] + '.' + item[1];
item = parseFloat(item);
return item;
});
if (sort == 'down') data.reverse();
return data;
}, _sumTimeSort: function (data, sort) {
data = _.sortBy(data, function (item) {
return parseInt(item.use_time);
});
if (sort == 'down') data.reverse();
return data;
}, _priceSort: function (data, sort) {
data = _.sortBy(data, function (item) {
return item.min_price;
});
if (sort == 'down') data.reverse();
return data;
}, //获取导航栏排序后的数据
getSortData: function (data) {
var tmp = [];
var sort = this.entity.get(); for (var k in sort) {
if (sort[k].length > 0) {
tmp = this['_' + k + 'Sort'](data, sort[k])
return tmp;
}
}
}, //复杂的业务数据处理,为了达到产品的需求,这段代码逻辑与业务相关
//这段数据处理的代码过长(超过50行就过长),应该重构掉
formatData: function (data) {
var item, seat;
var typeMap = {
'g': 'g',
'd': 'd',
't': 't',
'c': 'g'
}; //出发时间对应的分钟数
var fromMinute = 0; //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
var d = 1464192000000;
var date = new Date();
var now = parseInt(date.getTime() / 1000);
date.setTime(d);
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var toBegin;
var seatName, seatIndex, iii; //处理坐席问题,仅显示二等座,一等座,特等座 无座
// 二等座 一等座 商务座 无座 动卧 特等座
var my_seats = {};
var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座']; for (var i = 0, len = data.length; i < len; i++) {
fromMinute = data[i].from_time.split(':');
fromMinute[0] = fromMinute[0] + '';
fromMinute[1] = fromMinute[1] + '';
if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
fromMinute = parseInt(date.getTime() / 1000)
toBegin = parseInt((fromMinute - now) / 60); data[i].toBegin = toBegin; //处理车次类型问题
data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; seat = data[i].seats;
//所有余票
data[i].sum_ticket = 0;
//最低价
data[i].min_price = null; for (var j = 0, len1 = seat.length; j < len1; j++) {
if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
data[i].sum_ticket += parseInt(seat[j].seat_yupiao); //坐席问题如果坐席不包括上中下则去掉
seatName = seat[j].seat_name;
//去掉上中下
seatName = seatName.replace(/上|中|下/g, '');
if (!my_seats[seatName]) {
my_seats[seatName] = parseInt(seat[j].seat_yupiao);
} else {
my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
}
}
//这里myseat为对象,需要转换为数组
//将定制坐席转为排序后的数组
data[i].my_seats = [];
for (iii = 0; iii < seatSort.length; iii++) {
if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
name: seatSort[iii],
yupiao: my_seats[seatSort[iii]]
});
} my_seats = {};
} return data;
}, //完成所有的筛选条件,逻辑比较重
getViewModel: function () {
var data = this.formatData(listData);
data = this.getSortData(data);
return {data: data};
} }); });

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

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

 <article class="cm-page page-list" id="main">
<div class="js_sort_wrapper sort-bar-wrapper">
<mySortBar :entity="sortEntity"></mySortBar>
</div>
<myList :entity="listEntity" :sort="sort"></myList>
</article>
 (function () {
require.config({
paths: {
'text': 'libs/require.text', 'AbstractView': 'js/view',
'AbstractEntity': 'js/entity',
'ModuleView': 'js/module'
}
}); require(['pages/list.label'], function (List) {
var list = new List();
list.show();
});
})();

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

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

 this.sortModule = new SortModule({
//view: this,
//selector: '.js_sort_wrapper',
//sortEntity: this.sortEntity
});

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

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

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

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

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

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

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

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

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

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

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

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

 _initComponent: function() {
var key, components = this.$components;
for(key in components) {
//这里实例化的过程有点复杂,首先将页面的标签做一个替换
var s = ''
}
},
 _initComponent: function() {
var key, components = this.$components;
var el, attributes, attr, param, clazz, i, len, tmp, id, name; //这里实例化的过程有点复杂,首先将页面的标签做一个替换
for(key in components) {
param = {};
clazz = components[key];
//由原型链上获取根元素要注入给子组件的属性(这个实现好像不太好)
attributes = clazz.prototype.props; //首先获取标签dom元素,因为html是不区分大小写的,这里将标签小写
el = this.$(key.toLowerCase());
if(!el[0]) continue; if(attributes) {
for (i = 0, len = attributes.length; i < len; i++) {
attr = attributes[i];
name = el.attr(':' + attr);
param[attr] = this[name] || name;
}
} //创建一个空div去替换原来的标签
id = _.uniqueId('componenent-id-');
tmp = $('<div component-name="' + key + '" id="' + id + '"></div>');
tmp.insertBefore(el);
el.remove();
param.selector = '#' + id;
param.view = this;
this[key] = new components[key](param);
} },

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

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

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

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

总结

这里首先根据上次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稍微做简单点,也只包含顶部导航和列表即可:

 <!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui"/>
<meta content="yes" name="apple-mobile-web-app-capable"/>
<meta content="black" name="apple-mobile-web-app-status-bar-style"/>
<meta name="format-detection" content="telephone=no"/>
<link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
<link href="./pages/list.css" rel="stylesheet" type="text/css"/>
<title>组件化</title>
</head>
<body>
<div class="cm-header">
<h1 class="cm-page-title js_title"> 组件化Demo </h1>
</div> <article class="cm-page page-list" id="main">
</article> <script src="./libs/react-with-addons.js"></script>
<script src="./libs/JSXTransformer.js"></script>
<script type="text/javascript" src="./pages/list.data.js"></script>
<script type="text/javascript" src="./libs/underscore.js"></script>
<script type="text/jsx"> var MySortBar = React.createClass({
getInitialState: function() {
return {
time: 'up',
sumTime: '',
price: ''
};
}, resetData: function () {
this.setState({
time: '',
sumTime: '',
price: ''
});
}, setTime: function () {
this._setData('time');
}, setSumTime: function () {
this._setData('sumTime');
}, setPrice: function () {
this._setData('price');
}, _setData: function (key) {
var param = {}; //如果设置当前key存在,则反置,否则清空筛选,设置默认值
if (this.state[key] != '') {
if (this.state[key] == 'up') param[key] = 'down';
else param[key] = 'up';
} else {
this.resetData();
param[key] = 'down';
}
this.setState(param);
}, _getClassName: function(icon) {
return 'icon-sort ' + icon;
}, render: function () {
return (
<ul className="bus-tabs sort-bar js_sort_item">
<li className="tabs-item" onClick={this.setTime} >出发时间<i className={this._getClassName(this.state.time)}></i></li>
<li className="tabs-item" onClick={this.setSumTime} >耗时<i className={this._getClassName(this.state.sumTime)}></i></li>
<li className="tabs-item" onClick={this.setPrice} >价格<i className={this._getClassName(this.state.price)}></i></li>
</ul>
);
} }); var Seat = React.createClass({
render: function () {
var seat = this.props.seat; return (
<span >{seat.name}({seat.yupiao }) </span>
);
}
}); var Item = React.createClass({
render: function () { var item = this.props.item;
var mapping = {
'g': '高速',
't': '特快',
'd': '高速动车',
'c': '城际高铁',
'z': '直达'
}; var seats = item.my_seats.map(function(item){
return <Seat seat={item}/>;
}); return (
<li className="bus-list-item ">
<div className="bus-seat">
<span className=" fl">{item.train_number } | {mapping[item.my_train_number] || '其它'} </span>
<span className=" fr">{parseInt(item.use_time / 60) + '小时' + item.use_time % 60 + '分'}</span>
</div>
<div className="detail">
<div className="sub-list set-out">
<span className="bus-go-off">{item.from_time}</span> <span className="start"><span className="icon-circle s-icon1">
</span>{item.from_station }</span> <span className="fr price">¥{item.min_price}起</span>
</div>
<div className="sub-list">
<span className="bus-arrival-time">{item.to_time}</span> <span className="end"><span className="icon-circle s-icon2">
</span>{item.to_station}</span> <span className="fr ">{item.sum_ticket}张</span>
</div>
</div>
<div className="bus-seats-info" >
{seats}
</div>
</li>
);
}
}); var MyList = React.createClass({ formatData: function (data) { var item, seat;
var typeMap = {
'g': 'g',
'd': 'd',
't': 't',
'c': 'g'
}; //出发时间对应的分钟数
var fromMinute = 0; //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
var d = 1464192000000;
var date = new Date();
var now = parseInt(date.getTime() / 1000);
date.setTime(d);
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var toBegin;
var seatName, seatIndex, iii; //处理坐席问题,仅显示二等座,一等座,特等座 无座
// 二等座 一等座 商务座 无座 动卧 特等座
var my_seats = {};
var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座']; for (var i = 0, len = data.length; i < len; i++) {
fromMinute = data[i].from_time.split(':');
fromMinute[0] = fromMinute[0] + '';
fromMinute[1] = fromMinute[1] + '';
if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
fromMinute = parseInt(date.getTime() / 1000)
toBegin = parseInt((fromMinute - now) / 60); data[i].toBegin = toBegin; //处理车次类型问题
data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; seat = data[i].seats;
//所有余票
data[i].sum_ticket = 0;
//最低价
data[i].min_price = null; for (var j = 0, len1 = seat.length; j < len1; j++) {
if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
data[i].sum_ticket += parseInt(seat[j].seat_yupiao); //坐席问题如果坐席不包括上中下则去掉
seatName = seat[j].seat_name;
//去掉上中下
seatName = seatName.replace(/上|中|下/g, '');
if (!my_seats[seatName]) {
my_seats[seatName] = parseInt(seat[j].seat_yupiao);
} else {
my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
}
}
//这里myseat为对象,需要转换为数组
//将定制坐席转为排序后的数组
data[i].my_seats = [];
for (iii = 0; iii < seatSort.length; iii++) {
if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
name: seatSort[iii],
yupiao: my_seats[seatSort[iii]]
});
} my_seats = {};
} return data;
}, render: function () { var main;
var data = this.formatData(this.props.data); main = data.map(function(item) {
return <Item item={item}/>;
}); return (
<ul className="bus-list js_bus_list ">
{main}
</ul>
);
} }); var data = getListData(); React.render(
<div>
<div className="js_sort_wrapper sort-bar-wrapper">
<MySortBar />
</div>
<MyList data={data} />
</div>,
document.getElementById('main')
); </script> </body>
</html>

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

React 组件通信

这里React子组件之间如何通信暂没有研究出来,所以将需要通信的数据做到了父组件中
 <!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui"/>
<meta content="yes" name="apple-mobile-web-app-capable"/>
<meta content="black" name="apple-mobile-web-app-status-bar-style"/>
<meta name="format-detection" content="telephone=no"/>
<link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
<link href="./pages/list.css" rel="stylesheet" type="text/css"/>
<title>组件化</title>
</head>
<body>
<div class="cm-header">
<h1 class="cm-page-title js_title"> 组件化Demo </h1>
</div> <article class="cm-page page-list" id="main">
</article> <script src="./libs/react-with-addons.js"></script>
<script src="./libs/JSXTransformer.js"></script>
<script type="text/javascript" src="./pages/list.data.js"></script>
<script type="text/javascript" src="./libs/underscore.js"></script>
<script type="text/jsx"> var MySortBar = React.createClass({
_getClassName: function(icon) {
return 'icon-sort ' + icon;
}, render: function () {
var state = this.props.state;
return (
<ul className="bus-tabs sort-bar js_sort_item">
<li className="tabs-item" onClick={this.props.setTime} >出发时间<i className={this._getClassName(state.time)}></i></li>
<li className="tabs-item" onClick={this.props.setSumTime} >耗时<i className={this._getClassName(state.sumTime)}></i></li>
<li className="tabs-item" onClick={this.props.setPrice} >价格<i className={this._getClassName(state.price)}></i></li>
</ul>
);
} }); var Seat = React.createClass({
render: function () {
var seat = this.props.seat; return (
<span >{seat.name}({seat.yupiao }) </span>
);
}
}); var Item = React.createClass({
render: function () { var item = this.props.item;
var mapping = {
'g': '高速',
't': '特快',
'd': '高速动车',
'c': '城际高铁',
'z': '直达'
}; var seats = item.my_seats.map(function(item){
return <Seat seat={item}/>;
}); return (
<li className="bus-list-item ">
<div className="bus-seat">
<span className=" fl">{item.train_number } | {mapping[item.my_train_number] || '其它'} </span>
<span className=" fr">{parseInt(item.use_time / 60) + '小时' + item.use_time % 60 + '分'}</span>
</div>
<div className="detail">
<div className="sub-list set-out">
<span className="bus-go-off">{item.from_time}</span> <span className="start"><span className="icon-circle s-icon1">
</span>{item.from_station }</span> <span className="fr price">¥{item.min_price}起</span>
</div>
<div className="sub-list">
<span className="bus-arrival-time">{item.to_time}</span> <span className="end"><span className="icon-circle s-icon2">
</span>{item.to_station}</span> <span className="fr ">{item.sum_ticket}张</span>
</div>
</div>
<div className="bus-seats-info" >
{seats}
</div>
</li>
);
}
}); var MyList = React.createClass({ formatData: function (data) { var item, seat;
var typeMap = {
'g': 'g',
'd': 'd',
't': 't',
'c': 'g'
}; //出发时间对应的分钟数
var fromMinute = 0; //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
var d = 1464192000000;
var date = new Date();
var now = parseInt(date.getTime() / 1000);
date.setTime(d);
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var toBegin;
var seatName, seatIndex, iii; //处理坐席问题,仅显示二等座,一等座,特等座 无座
// 二等座 一等座 商务座 无座 动卧 特等座
var my_seats = {};
var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座']; for (var i = 0, len = data.length; i < len; i++) {
fromMinute = data[i].from_time.split(':');
fromMinute[0] = fromMinute[0] + '';
fromMinute[1] = fromMinute[1] + '';
if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
fromMinute = parseInt(date.getTime() / 1000)
toBegin = parseInt((fromMinute - now) / 60); data[i].toBegin = toBegin; //处理车次类型问题
data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; seat = data[i].seats;
//所有余票
data[i].sum_ticket = 0;
//最低价
data[i].min_price = null; for (var j = 0, len1 = seat.length; j < len1; j++) {
if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
data[i].sum_ticket += parseInt(seat[j].seat_yupiao); //坐席问题如果坐席不包括上中下则去掉
seatName = seat[j].seat_name;
//去掉上中下
seatName = seatName.replace(/上|中|下/g, '');
if (!my_seats[seatName]) {
my_seats[seatName] = parseInt(seat[j].seat_yupiao);
} else {
my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
}
}
//这里myseat为对象,需要转换为数组
//将定制坐席转为排序后的数组
data[i].my_seats = [];
for (iii = 0; iii < seatSort.length; iii++) {
if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
name: seatSort[iii],
yupiao: my_seats[seatSort[iii]]
});
} my_seats = {};
} return data;
}, _timeSort: function (data, sort) {
data = _.sortBy(data, function (item) {
item = item.from_time.split(':');
item = item[0] + '.' + item[1];
item = parseFloat(item);
return item;
});
if (sort == 'down') data.reverse();
return data;
}, _sumTimeSort: function (data, sort) {
data = _.sortBy(data, function (item) {
return parseInt(item.use_time);
});
if (sort == 'down') data.reverse();
return data;
}, _priceSort: function (data, sort) {
data = _.sortBy(data, function (item) {
return item.min_price;
});
if (sort == 'down') data.reverse();
return data;
}, //获取导航栏排序后的数据
getSortData: function (data) {
var tmp = [];
var sort = this.props.state; for (var k in sort) {
if (sort[k].length > 0) {
tmp = this['_' + k + 'Sort'](data, sort[k])
return tmp;
}
}
}, render: function () { var main;
var data = this.formatData(this.props.data);
data = this.getSortData(data); main = data.map(function(item) {
return <Item item={item}/>;
}); return (
<ul className="bus-list js_bus_list ">
{main}
</ul>
);
} }); var App = React.createClass({
getInitialState: function() {
return {
time: 'up',
sumTime: '',
price: ''
};
}, resetData: function () {
this.setState({
time: '',
sumTime: '',
price: ''
});
}, setTime: function () {
this._setData('time');
}, setSumTime: function () {
this._setData('sumTime');
}, setPrice: function () {
this._setData('price');
}, _setData: function (key) {
var param = {}; //如果设置当前key存在,则反置,否则清空筛选,设置默认值
if (this.state[key] != '') {
if (this.state[key] == 'up') param[key] = 'down';
else param[key] = 'up';
} else {
this.resetData();
param[key] = 'down';
}
this.setState(param);
}, formatData: function (data) { var item, seat;
var typeMap = {
'g': 'g',
'd': 'd',
't': 't',
'c': 'g'
}; //出发时间对应的分钟数
var fromMinute = 0; //获取当前班车日期当前的时间戳,这个数据是动态的,这里写死了
var d = 1464192000000;
var date = new Date();
var now = parseInt(date.getTime() / 1000);
date.setTime(d);
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var toBegin;
var seatName, seatIndex, iii; //处理坐席问题,仅显示二等座,一等座,特等座 无座
// 二等座 一等座 商务座 无座 动卧 特等座
var my_seats = {};
var seatSort = ['二等座', '一等座', '硬座', '硬卧', '软卧', '商务座', '无座', '动卧', '特等座', '软座']; for (var i = 0, len = data.length; i < len; i++) {
fromMinute = data[i].from_time.split(':');
fromMinute[0] = fromMinute[0] + '';
fromMinute[1] = fromMinute[1] + '';
if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1);
if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1);
date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0);
fromMinute = parseInt(date.getTime() / 1000)
toBegin = parseInt((fromMinute - now) / 60); data[i].toBegin = toBegin; //处理车次类型问题
data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; seat = data[i].seats;
//所有余票
data[i].sum_ticket = 0;
//最低价
data[i].min_price = null; for (var j = 0, len1 = seat.length; j < len1; j++) {
if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price);
data[i].sum_ticket += parseInt(seat[j].seat_yupiao); //坐席问题如果坐席不包括上中下则去掉
seatName = seat[j].seat_name;
//去掉上中下
seatName = seatName.replace(/上|中|下/g, '');
if (!my_seats[seatName]) {
my_seats[seatName] = parseInt(seat[j].seat_yupiao);
} else {
my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao);
}
}
//这里myseat为对象,需要转换为数组
//将定制坐席转为排序后的数组
data[i].my_seats = [];
for (iii = 0; iii < seatSort.length; iii++) {
if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({
name: seatSort[iii],
yupiao: my_seats[seatSort[iii]]
});
} my_seats = {};
} return data;
}, render: function () { var main;
var data = this.formatData(this.props.data);
main = data.map(function(item) {
return <Item item={item}/>;
}); return (
<div>
<div className="js_sort_wrapper sort-bar-wrapper">
<MySortBar state={this.state} setTime={this.setTime} setSumTime={this.setSumTime} setPrice={this.setPrice}/>
</div>
<MyList data={data} state={this.state} />
</div>
);
} }); var data = getListData(); React.render(
<App data={data}/>,
document.getElementById('main')
); </script> </body>
</html>

总结

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

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

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

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

从DOM操作看Vue&React的前端组件化,顺带补齐React的demo的更多相关文章

  1. 谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo

    前言 前端已经过了单兵作战的时代了,现在一个稍微复杂一点的项目都需要几个人协同开发,一个战略级别的APP的话分工会更细,比如携程: 携程app = 机票频道 + 酒店频道 + 旅游频道 + ..... ...

  2. Vue.js:轻量高效的前端组件化方案

    转发一篇尤老师对vue.js的介绍,了解vue.js的来龙去脉.不过现在已经是2.0了,也有添加一些新的东西,当然有些东西也改了. Vue.js:轻量高效的前端组件化方案 Vue.js 是我在2014 ...

  3. Webpack+Vue+ES6 前端组件化开发mobile-multi-page应用实战总结

    本文版权归博客园和作者吴双本人共同所有 转载和爬虫请注明原文地址 www.cnblogs.com/tdws 一.写在前面 项目上线有一段时间了,一个基于webpack+vue+ES6的手机端多页面应用 ...

  4. Webpack+Vue+ES6 前端组件化开发mobile-multi-page应用实战总结和踩坑

    本文版权归博客园和作者吴双本人共同所有 转载和爬虫请注明原文地址 www.cnblogs.com/tdws 一.写在前面 项目上线有一段时间了,一个基于webpack+vue+ES6的手机端多页面应用 ...

  5. 如何通过 Vue+Webpack 来做通用的前端组件化架构设计

    目录:   1. 架构选型     2. 架构目录介绍     3. 架构说明     4. 招聘消息 目前如果要说比较流行的前端架构哪家强,屈指可数:reactjs.angularjs.emberj ...

  6. 大话大前端时代(一) —— Vue 与 iOS 的组件化

    序 今年大前端的概念一而再再而三的被提及,那么大前端时代究竟是什么呢?大前端这个词最早是因为在阿里内部有很多前端开发人员既写前端又写 Java 的 Velocity 模板而得来,不过现在大前端的范围已 ...

  7. 前端组件化Polymer入门教程(1)——初识&&安装

    前端组件化Polymer入门教程目录: 前端组件化Polymer入门教程(1)--初识&&安装 前端组件化Polymer入门教程(2)--快速入门 前端组件化Polymer入门教程(3 ...

  8. vue(9)—— 组件化开发 - webpack(3)

    前面两个终于把webpack相关配置解析完了.现在终于进入vue的开发了 vue组件化开发预热 前期准备 创建如下项目: app.js: footer.js: main.js: webpack.con ...

  9. 前端组件化-Web Components【转】

    以下全部转自:http://www.cnblogs.com/pqjwyn/p/7401918.html 前端组件化的痛点在前端组件化横行的今天,确实极大的提升了开发效率.不过有一个问题不得不被重视,拟 ...

随机推荐

  1. Android中Activity运行时屏幕方向与显示方式详解

    现在我们的手机一般都内置有方向感应器,手机屏幕会根据所处位置自动进行横竖屏切换(前提是未锁定屏幕方向).但有时我们的应用程序仅限在横屏或者竖屏状态下才可以运行,此时我们需要锁定该程序Activity运 ...

  2. 真正的汉化-PowerDesigner 16.5 汉化

    一.背景 经常使用PowerDesigner,之前使用15版本,后来16出来后,就一直在使用16,不过一直是英文.一些同事对使用英文版总显示有些吃力. 遍寻百度.必应,都没有找到真正的针对版本16的汉 ...

  3. [Hadoop in Action] 第5章 高阶MapReduce

    链接多个MapReduce作业 执行多个数据集的联结 生成Bloom filter   1.链接MapReduce作业   [顺序链接MapReduce作业]   mapreduce-1 | mapr ...

  4. 项目游戏开发日记 No.0x00000

    14软二杨近星(2014551622) ---恢复内容开始--- 2016-03-17 从开始迈进软件工程专业, 已经快两年了, 记得当初选择软件的理由是, 我要学去做东西, 我享受开发过程. 两年来 ...

  5. 【Knockout.js 学习体验之旅】(1)ko初体验

    前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...

  6. 【技巧】使用weeman来做一个钓鱼网页

    本文来自网友836834283 对玄魂工作室的投稿. 工具项目地址:https://github.com/Hypsurus/weeman/ 克隆地址:https://github.com/Hypsur ...

  7. Netty5使用自签证书实现SSL安全连接

    这次使用的Netty是最新的5.0 Alpha2版本,下载地址是:http://dl.bintray.com/netty/downloads/netty-5.0.0.Alpha2.tar.bz2,发布 ...

  8. JVM虚拟机结构

    JVM的主要结构如下图所示,图片引用自舒の随想日记. 方法区和堆由所有线程共享,其他区域都是线程私有的 程序计数器(Program Counter Register) 类似于PC寄存器,是一块较小的内 ...

  9. ABP框架 - 依赖注入

    文档目录 本节内容: 什么是依赖注入 传统方式的问题 解决方案 构造器注入模式 属性注入模式 依赖注入框架 ABP 依赖注入基础 注册依赖 约定注入 辅助接口 自定义/直接 注册 使用IocManag ...

  10. [转载]:STM32为什么必须先配置时钟再配置GPIO

    转载来源 :http://blog.csdn.net/fushiqianxun/article/details/7926442 [原创]:我来添两句,就是很多同学(包括我)之前搞低端单片机,到了stm ...