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

时间:2021-08-23 23:41:43

前言

接上文:谈谈我对前端组件化中“组件”的理解,顺带写个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. 谈谈我对前端组件化中&OpenCurlyDoubleQuote;组件”的理解,顺带写个Vue与React的demo

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

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

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

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

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

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

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

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

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

  6. 大话大前端时代&lpar;一&rpar; —— Vue 与 iOS 的组件化

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

  7. 前端组件化Polymer入门教程&lpar;1&rpar;——初识&amp&semi;&amp&semi;安装

    前端组件化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. 如何自定义jupyter notebook的主题

    临时性的改变一个jupyter的主题 参考这个实现,只要在notebook里运行这段代码就行了,能让所有的cell都能够显示黑色背景 一个更为完备的工具 参考这个方案: 安装jupyter-theme ...

  2. mac安装mongodb

    一,安装方法1 ,下载mongodb 1,官网下载mongodb程序 https://www.mongodb.org/downloads#production​ 2,解压后启动mongodb服务 下载 ...

  3. mbps

    Mbps=Mbit/s即兆比特每秒.Million bits per second的缩写 传输速率是指设备的的数据交换能力,也叫“带宽”,单位是Mbps(兆位/秒),目前主流的集线器带宽主要有10Mb ...

  4. Cloudera CDH 、Impala本地通过Parcel安装配置详解及什么是Parcel

    本文引用自:Cloudera CDH .Impala本地通过Parcel安装配置详解及什么是Parcelhttp://www.aboutyun.com/forum.php?mod=viewthread ...

  5. LDF文件过大的解决办法

    检查扎兰屯服务器的时候,发现其中一个分区的原空间有300多个G,但只余下了80多个G.检查了一下,发现某库ldf文件过大,竟然达到了280多个G. 这如何得了,再这样下去,硬盘怎能受得了? 尝试用收缩 ...

  6. windows2008 apache2&period;4 tomcat-7多域名绑定环境配置

    =====================软件清单Apache2.4.33apache-tomcat-7.0.85===================== 1.安装apache    1.1下载ap ...

  7. Spark生态以及原理

    spark 生态及运行原理 Spark 特点 运行速度快 => Spark拥有DAG执行引擎,支持在内存中对数据进行迭代计算.官方提供的数据表明,如果数据由磁盘读取,速度是Hadoop MapR ...

  8. shell脚本作为cgi程序--以web版man为例

    man.cgi源码 #! /bin/sh eval `sh proccgi.sh $*` echo "Content-type: text/html" echo echo echo ...

  9. Ubuntu 18&period;04版本下安装网易云音乐

    这是我迄今为止发现的最完美的解决方法,不用改任何东西,只需要安装然后打开即可,后台也有. 参考:http://archive.ubuntukylin.com:10006/ubuntukylin/poo ...

  10. poj 3463&sol;hdu 1688 求次短路和最短路个数

    http://poj.org/problem?id=3463 http://acm.hdu.edu.cn/showproblem.php?pid=1688 求出最短路的条数比最短路大1的次短路的条数和 ...