前言

接上文:谈谈我对前端组件化中“组件”的理解,顺带写个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. ViewController respondsToSelector 错误的解决方法

    原因解析:(来自别人博客分析)某个公共类或系统提供的控件,存在delegate方法,当创建此公共控件的容器类已经销毁,而这个控件对应的服务是在其它run loop中进行的,控件销毁或者需要进行状态通知 ...

  2. nginx安装

    nginx工作模式-->1个master+n个worker进程 安装nginx的所需pcre库[用于支持rewrite模块] 下载软件方法: 搜索 pcre  download 网址:http: ...

  3. Xamarin.Android广播接收器与绑定服务

    一.前言 学习了前面的活动与服务后,你会发现服务对于活动而言似乎就是透明的,相反活动对于服务也是透明的,所以我们还需要一中机制能够将服务和活动之间架起一座桥梁,通过本节的学习,你将会学到广播与绑定服务 ...

  4. [Solr] (源) Solr与MongoDB集成,实时增量索引

    一. 概述 大量的数据存储在MongoDB上,需要快速搜索出目标内容,于是搭建Solr服务. 另外一点,用Solr索引数据后,可以把数据用在不同的项目当中,直接向Solr服务发送请求,返回xml.js ...

  5. Entity Framework 6 Recipes 2nd Edition(9-2)译->用WCF更新单独分离的实体

    9-2. 用WCF更新单独分离的实体 问题 你想通过WCF为一个数据存储发布查询,插入,删除和修改,并且使这些操作尽可能地简单 此外,你想通过Code First方式实现EF6的数据访问管理 解决方案 ...

  6. 原生ajax实现登录(一部分代码)

        <script type="text/javascript">          function ajax() {          //先声明一个异步请求对 ...

  7. esayui

  8. (转)倾力总结40条常见的移动端Web页面问题解决方案

    原文链接:戳这里 1.安卓浏览器看背景图片,有些设备会模糊.   用同等比例的图片在PC机上很清楚,但是手机上很模糊,原因是什么呢? 经过研究,是devicePixelRatio作怪,因为手机分辨率太 ...

  9. NodeJs+http+fs+request+cheerio 采集,保存数据,并在网页上展示(构建web服务器)

    目的: 数据采集 写入本地文件备份 构建web服务器 将文件读取到网页中进行展示 目录结构: package.json文件中的内容与上一篇一样:NodeJs+Request+Cheerio 采集数据 ...

  10. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(16)-权限管理系统-漂亮的验证码

    系列目录 我们上一节建了数据库的表,但我发现很多东西还未完善起来,比如验证码,我们先做好验证码吧,验证码我们再熟悉不过了,为了防止恶意的登录,我们必须在登录页面加入验证码,下面我将分享一个验证码,这个 ...