1.React简介
- 仅仅是UI
许多人使用React作为MVC架构的V层。 尽管React并没有假设过你的其余技术栈, 但它仍可以作为一个小特征轻易地在已有项目中使用。 - 虚拟DOM
React为了更高超的性能而使用虚拟DOM作为其不同的实现。 它同时也可以由服务端Node.js渲染 - 而不需要过重的浏览器DOM支持。
组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升。 - 数据流
React实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
React中文文档链接http://reactjs.cn/react/index.html
2.为什么使用React
React是一个 Facebook 和 Instagram 用来创建用户界面的 JavaScript 库。很多人认为 React 是 MVC 中的 V(视图)。我们创造 React 是为了解决一个问题:构建随着时间数据不断变化的大规模应用程序。为了达到这个目标,React 采用下面两个主要的思想。
- 简单
仅仅只要表达出你的应用程序在任一个时间点应该长的样子,然后当底层的数据变了,React 会自动处理所有用户界面的更新。 - 声明式
数据变化后,React 概念上与点击“刷新”按钮类似,但仅会更新变化的部分。
3.数据呈现
用户界面能做的最基础的事就是呈现一些数据。React 让显示数据变得简单,当数据变化时,用户界面会自动同步更新。
快速开始
新建一个名为 hello-react.html 的文件,内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<html>
<head>
<meta charset="utf-8">
<title>Hello React</title>
<script type="text/javascript" src="http://fb.me/react-0.14.7.js"></script>
<script type="text/javascript" src="http://fb.me/JSXTransformer-0.14.7.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/jsx">
var HelloWorld = React.createClass({
render: function() {
return (
<p>
Hello,<input type="text" placeholder="Your name here" />!
It is {this.props.date.toTimeString()}
</p>
);
}
});
setInterval(function() {
React.render(
<HelloWorld date={new Date()} />,
document.getElementById('example')
);
}, 500);
</script>
</body>
</html>
注意事项
- ReactJs是基于组件化的开发,所以最终你的页面应该是由若干个小组件组成的大组件。
- 可以通过属性,将值传递到组件内部,同理也可以通过属性将内部的结果传递到父级组件;要对某些值的变化做DOM操作的,要把这些值放到state中。
为组件添加外部css样式时,类名应该写成className而不是class;添加内部样式时
1
2style={{opacity: this.state.opacity}}; //正确
style="opacity:{this.state.opacity};" //错误组件名称首字母必须大写。
- 变量名用
{}包裹,且不能加双引号。
响应式更新(Reactive Updates)
在浏览器中打开 hello-react.html ,在输入框输入你的名字。你会发现 React 在用户界面中只改变了时间,你在输入框的输入内容会保留着,即使你没有写任何代码来完成这个功能。React 也为你解决了这个问题,做了正确的事。
我们想到的解决方案是React 是不会去操作 DOM 的,除非不得不操作 DOM 。它用一种更快的内置仿造的 DOM 来操作差异,为你计算出效率最高的 DOM 改变。
这个组件的输入被称为 props - “properties”的缩写。它们通过 JSX 语法进行参数传递。你必须知道,在组件里这些属性是不可直接改变的,也就是说 this.props 是只读的。
组件就像是函数
React 组件非常简单。你可以认为它们就是简单的函数,接受 props 和 state 作为参数,然后渲染出 HTML。正是由于它们如此简单,使得它们非常容易理解。
4.富交互性的动态用户界面
简单例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked? 'like': 'haven\'t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
React.render(
<LikeButton />,
document.getElementById('example')
);
事件处理与合成事件(Synthetic Events)
React 里只需把事件处理器(event handler)以骆峰命名(camelCased)形式当作组件的 props 传入即可,就像使用普通 HTML 那样。React 内部创建一套合成事件系统来使所有事件在 IE8 和以上浏览器表现一致。也就是说,React 知道如何冒泡和捕获事件,而且你的事件处理器接收到的 events 参数与 W3C 规范一致,无论你使用哪种浏览器。
如果需要在手机或平板等触摸设备上使用 React,需要调用React.initializeTouchEvents(true); 启用触摸事件处理。
幕后原理
在幕后,React 做了一些操作来让代码高效运行且易于理解。
Autobinding: 在 JavaScript 里创建回调的时候,为了保证 this 的正确性,一般都需要显式地绑定方法到它的实例上。有了 React,所有方法被自动绑定到了它的组件实例上。React 还缓存这些绑定方法,所以 CPU 和内存都是非常高效。而且还能减少打字!
事件代理 : React 实际并没有把事件处理器绑定到节点本身。当 React 启动的时候,它在最外层使用唯一一个事件监听器处理所有事件。当组件被加载和卸载时,只是在内部映射里添加或删除事件处理器。当事件触发,React 根据映射来决定如何分发。当映射里处理器时,会当作空操作处理。
组件其实是状态机(State Machines)
React 把用户界面当作简单状态机。把用户界面想象成拥有不同状态然后渲染这些状态,可以轻松让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。React 来决定如何最高效地更新 DOM。
State工作原理
常用的通知 React 数据变化的方法是调用 setState(data, callback)。这个方法会合并(merge) data 到 this.state,并重新渲染组件。渲染完成后,调用可选的 callback回调。大部分情况下不需要提供 callback,因为 React 会负责把界面更新到最新状态。
5.应用实例
以安信农业保险管理系统保单列表为例:(当前农保系统框架重构工作已全部完成)
源码分析1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32render() {
let handler4policy = {
handlerArrow: this.handlerArrow.bind(this),
handler4bd: {
handlerFolder: this.handlerFolder.bind(this),
handler4land: {
deleteLand: this.deleteLand.bind(this)
}
}
}
return (
<div className="item-wrap" style={this.props.css}>
<div className="item-search-wrap">
<input type="text" className='item-search-input' ref='search' value={this.state.search} placeholder="编号/证件号/姓名/电话" onChange={this.handlerInputChange.bind(this)} onKeyDown={this.handlerKeyDown.bind(this)}/>
<div className="item-search-btn" onClick={this.searchItem.bind(this)}>
<img className="item-search-logo" src="img/search.png" />
</div>
</div>
<div className="policy-add-btn" title="新建保单" onClick={this.handlerNewPolicy.bind(this)}>
<img className="item-insert-logo" src="img/add_2.png" />
<span className="item-insert-txt">新建保单</span>
</div>
<div className="item-contain">
<ul className = "item-ul">
{this.state.items.map((item, idx) => {
return (<PolicyMod key = {item.policy.CODING} {...this.config} {...item} {...handler4policy} index = {idx} />);
})}
</ul>
</div>
</div>
);
}
保单搜索
通过点击保单查询图标或输入框的Enter键盘事件触发SearchItem函数接受查询信息,进而以查询信息作为关键字调用农保保单查询接口获取保单列表的JSON数据,预处理数据后通过调用React框架的setState函数更新状态机完成列表的刷新操作,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21handlerKeyDown(event) {
let [target = event.target] = [event.srcElement];
if(event.keyCode === 13) {
this.search(target.value);
}
}
searchItem(event) {
this.search(this.refs.search.value);
}
search(val) {
this.setState({search: ''});
connect.fuzzyQuery(val, json => {
this.refreshList(json);
if(json.length > 0 && json[0].bdArr.length > 0) {
let bdxq = json[0].bdArr[0].bdxq;
Map.map.zoomTo(parseFloat(bdxq['X']), parseFloat(bdxq['Y']), { 'zl': 1 });
}
});
}
这里this.state.items为保单列表展示所提供的数据(JSON数据格式),一些可能改变列表内容的交互行为最终表现为改变该组件的state值(即组件的当前状态)即可达到轻松渲染页面的效果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28refreshList(json, fid) {
this.pretreat(json, fid);
Map.getVectorLandData();
this.setState({items: json});
}
pretreat(items, fid) {
Map.curListBdArr = [];
Map.exceptFIDArr = [];
for (let i = 0; i < items.length; i++) {
let item = items[i];
item.isOpen = this.config.defaultOpen;
for (let j = 0; j < item.bdArr.length; j++) {
let bd = item.bdArr[j];
bd.isOpen = this.config.defaultOpen;
Map.curListBdArr.push(bd.bdxq.ID);
for (let z = 0; z < bd.landArr.length; z++) {
let land = bd.landArr[z];
land.highlight = false;
if(fid && land.FID === fid) {
land.highlight = true;
}
let cp = this._calCenterPoint(land.XMIN, land.XMAX, land.YMIN, land.YMAX);
land.centerPoint = `${cp.x},${cp.y}`;
}
}
}
}
保单操作

当通过点击对话框中相应的事件完成对保单的添加、更新以及删除操作的响应时,利用获取到的事件调用列表组件的保单事件接受函数完成相应的业务操作并更新数据库,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49alterPolicy_ex(type, detail) {
const plTAB = config.policyTab;
this._handlerDateForm(detail);
let params = {Fields: [], Data: [[]]};
for(let key of Object.keys(detail)) {
if(key === 'CODING') {
params.Fields.unshift(key);
params.Data[0].unshift(detail[key]);
continue;
}
params.Fields.push(key);
params.Data[0].push(detail[key]);
}
if(type === 'INSERT') {
detail.USRID = sessionStorage.getItem('usrName');
detail.AREA = 0;
params.Fields.push('USRID', 'AREA');
params.Data[0].push(detail.USRID, detail.AREA);
let plJson = { policy: detail, bdArr: [], isOpen: true };
this.state.items.push(plJson);
toolUtil.recordAdd(config.dbName, plTAB, params, () => {
toolUtil.newalertDiv('添加成功');
this.setState({items: this.state.items});
});
} else {
this.state.items.forEach(item => {
if (item.policy.CODING === detail.CODING) {
item.policy = detail;
}
});
toolUtil.recordUpdate(config.dbName, plTAB, params, () => {
toolUtil.newalertDiv('保存成功');
this.setState({items: this.state.items});
});
}
}
deletePolicy_ex(coding) {
let curInsFeas = Map.curWindowInsureFeas;
Map.curWindowInsureFeas = curInsFeas.filter(fea => {
if (fea.fields.CODING == coding) Map.insure_Featurelayer.removeFeature(fea);
return (fea.fields.CODING != coding);
});
this.state.items = this.state.items.filter(item => item.policy.CODING !== coding);
this.setState({items: this.state.items});
connect.deletePolicyByCoding(coding, () => {
toolUtil.newalertDiv('删除成功');
});
}
同样的,在处理不同的事件操作过程中通过改变组件的state值(这里是state.items),添加时push一条记录、更新替换一条记录、以及删除时剔除一条记录的方式,统一更改state值,再一次调用setState函数完成保单列表的刷新工作,渲染保单。最后根据不同的需求增删改操作数据库,完成保单的操作任务。
标的操作

在完成对标的的添加、更新以及删除操作时,利用获取到的事件调用列表组件的标的事件接受函数完成相应的业务操作并更新数据库,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49alterBd_ex(type, detail, point) {
const bdTAB = config.bdTab;
let params = {Fields: [], Data: [[]]};
for(let key of Object.keys(detail)) {
if(key === 'ID') {
params.Fields.unshift(key);
params.Data[0].unshift(detail[key]);
continue;
}
params.Fields.push(key);
params.Data[0].push(detail[key]);
}
if(type === 'INSERT') {
detail.AREA = 0;
params.Fields.push('AREA');
params.Data[0].push(detail.AREA);
let bdJson = { bdxq: detail, landArr: [], isOpen: true };
this.state.items.forEach(item => {
if (item.policy.CODING === detail.CODING) {
item.bdArr.push(bdJson);
}
});
toolUtil.recordAdd(config.dbName, bdTAB, params, () => {
toolUtil.newalertDiv('添加成功');
Map.map.zoomTo(parseFloat(point.X), parseFloat(point.Y), { zl: 1 });
let SQL = { 'fields': 'max(ID)', 'lyr': bdTAB, 'filter': '' };
toolUtil.recordQuery(config.dbName, SQL, msg => {
bdJson.bdxq.ID = msg[0]['max(ID)'];
this.setState({items: this.state.items});
});
});
} else {
this.state.items.forEach(item => {
if (item.policy.CODING === detail.CODING) {
let bdArr = item.bdArr;
bdArr.forEach(bd => {
if(bd.bdxq.ID === detail.ID) {
bd.bdxq = detail;
}
});
}
});
toolUtil.recordUpdate(config.dbName, bdTAB, params, () => {
toolUtil.newalertDiv('保存成功');
Map.map.zoomTo(parseFloat(point.X), parseFloat(point.Y), { zl: 1 });
this.setState({items: this.state.items});
});
}
}
同保单操作,在处理不同的事件操作过程中依然通过改变组件的state值,调用setState函数完成保单列表的刷新工作。最后根据不同的需求增删改操作数据库,完成标的的操作任务。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18deleteBd_ex(coding, bdid, area) {
let curInsFeas = Map.curWindowInsureFeas;
Map.curWindowInsureFeas = curInsFeas.filter(fea => {
if (fea.fields.BDID === bdid) Map.insure_Featurelayer.removeFeature(fea);
return (fea.fields.BDID !== bdid);
});
this.state.items.forEach(item => {
if (item.policy.CODING == coding) {
item.bdArr = item.bdArr.filter(bd => bd.bdxq.ID !== bdid);
item.policy.AREA = (parseFloat(item.policy.AREA) - area).toFixed(2);
connect.updateArea4Policy(coding, item.policy.AREA);
}
});
this.setState({items: this.state.items});
connect.deleteBdById(bdid, () => {
toolUtil.newalertDiv('删除成功');
});
}
地块操作
1.添加地块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45insertLand_ex(selectedfea) {
let [fields, params, land = {BDID: curbdid, CODING: curcoding}] = [selectedfea.fields, {Fields: landRelFields, Data: [[]]}];
landRelFields.forEach((fid) => {
if(fields[fid]) {
land[fid] = fields[fid];
params.Data[0].push(fields[fid]);
} else {
fid === 'BDID' && params.Data[0].push(curbdid);
fid === 'CODING' && params.Data[0].push(curcoding);
}
});
let cp = this._calCenterPoint(land.XMIN, land.XMAX, land.YMIN, land.YMAX);
land.centerPoint = `${cp.x},${cp.y}`;
selectedfea.addFields({'BDID': curbdid, 'CODING': curcoding});
Map.curWindowInsureFeas.push(selectedfea);
Map.curWindowInsureFeas.forEach(fea => {
if(fea.fields.BDID == curbdid) {
Map.insure_Featurelayer.addFeature(fea);
}
});
if(!toolUtil.isInArr(curbdid, Map.curListBdArr)) {
Map.curListBdArr.push(curbdid);
}
let area = 0;
this.state.items.forEach((item) => {
if (item.policy.CODING === curcoding) {
let bdArr = item.bdArr;
bdArr.forEach((bd) => {
if (bd.bdxq.ID === curbdid) {
bd.isOpen = true;
bd.landArr.push(land);
area = parseFloat(land.SHPAREA) / 666.67;
bd.bdxq.AREA = (parseFloat(bd.bdxq.AREA) + area).toFixed(2);
connect.updateArea4Bd(bd.bdxq.ID, bd.bdxq.AREA);
}
});
item.policy.AREA = (parseFloat(item.policy.AREA) + area).toFixed(2);
connect.updateArea4Policy(item.policy.CODING, item.policy.AREA);
}
});
this.setState({items: this.state.items});
toolUtil.recordAdd(config.dbName, config.landRelTab, params);
}
2.删除地块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30deleteLand(fid, bdid, coding) {
let area = 0;
Map.curWindowInsureFeas = Map.curWindowInsureFeas.filter(fea => {
if (fea.fields.FID === fid) Map.insure_Featurelayer.removeFeature(fea);
return (fea.fields.FID !== fid);
});
this.state.items.forEach((item) => {
if (item.policy.CODING === coding) {
let bdArr = item.bdArr;
bdArr.forEach((bd) => {
if (bd.bdxq.ID === bdid) {
bd.landArr = bd.landArr.filter(land => {
if (land.FID === fid) area = parseFloat(land.SHPAREA) / 666.67;
return land.FID !== fid;
});
bd.bdxq.AREA = (parseFloat(bd.bdxq.AREA) - area).toFixed(2);
connect.updateArea4Bd(bd.bdxq.ID, bd.bdxq.AREA);
}
});
item.policy.AREA = (parseFloat(item.policy.AREA) - area).toFixed(2);
connect.updateArea4Policy(item.policy.CODING, item.policy.AREA);
}
});
this.setState({items: this.state.items});
let params = {
'Fields': 'FID',
'Data': [fid]
};
toolUtil.recordDelete(config.dbName, config.landRelTab, params);
}
地块操作任务需要记录点选地块的要素信息(包括空间、属性信息)或地块的FID,在地图上高亮显示点选的保险地块后调用列表组件的接收函数将该要素信息添加到state中,或在state中剔除,更新组件state完成地块的添加或删除操作。最后更新数据库完成录入工作。
6.与其他框架的比较
React与Angular
- Reactjs作为view本身非常独立,不仅可以在浏览器上用,还可以在server端结合nodejs做模板。
- 除了独立,Reactjs还很灵活小巧,用起来像拼lego的积木,一个component接着另一个component。
在性能方面,由于运用了virtual dom技术,Reactjs只在调用setstate的时候会更新dom,而且还是先更新virtual dom,然后和实际dom比较,最后再更新实际dom。这个过程比起angularjs, knockoutjs的bind方式来说,一是更新dom的次数少,二是更新dom的内容少,速度肯定是快了的。另外Reactjs用了jsx,这个相当于半个新语言了。
angularjs是一个m-v-whateever framework。framework的一个特点就是很全面,除了m-v-whatever面面俱到,它还自带了很多$开头的service,$http, $route, $q(defer),$cookie等等。
- angularjs的架构清晰,分工明确,整个框架充满了DI的思路,耦合性非常低,对象都是被inject的,也就是说每个对象都可以轻易被替换而不影响其他对象。
React与Polymer
Polymer沿袭了正统的HTML+CSS+JS思路进行革新,很多组件化的功能靠浏览器自身的功能去支持,用浏览器原生的接口去做组件的集成,使用了效率最高的Vanilla JS ,易学易用。但是在兼容性方面不如React,React利用jsx集合Babel转码工具等,较好的解决了兼容性问题支持ie8及以上。
React与jQuery
jQuery的思路是以dom为中心,所以的操作都在围绕着dom的变化来操作。也就是jQuery的核心思想是dom可变。
React认为页面dom不可变,所以当状态和属性发生变化时会re-render dom。React会根据新的状态和属性生成新的VirtualDOM Tree然后和旧的VirtualDOM Tree做对比(类似于版本控制的机制)。通过对比计算出最小的更新代价,然后将这些更新的方法进入队列。