React.js那些事(入门篇)

关于React.js的相关背景知识以及它的核心概念已经在上一篇文章里面说过。在这一篇里面,我通过一个组件开发的实例简单介绍如何使用React进行web组件开发。

先来介绍一下这个打算实现的组件的功能:这是一个简单的三列表格,包含用户id,用户名,电话号码三列。当用户点击表头thead的单元时,表格会根据点中的列的字段进行升序排序。

三军未动,先上demo:

第一步:划分组件

首先,我们需要将整个组件划分为几个子组件,这是组件开发的重要思想——拆分组合。这是实现组件重用的关键技术。根据案例的原型,我将组件划分如下:
table partion
最外层的组件是我需要的目标组件 ——TableComponent,由绿色部分和黄色部分组成;
绿色部分为组件Thead,由三个相同的组件TheadCell构成(红色);
黄色部分为组件Tbody,每一行是一个组件Cell(蓝色);
红色部分—— TheadCell;
蓝色部分—— Cell;

第二步:组件绘制

组件绘制非常简单,首先需要引入react.js文件以及TSX转换器文件JSXTransformer.js。然后,创建一个组件只需要调用一个函数React.createClass()即可,可以从外层到里层构件或者从里层到外层构件。比如,我先构造组件TheadCell:

1
2
3
4
5
6
7
var TheadCell = React.createClass({
render: function (){
return (
<th ref="getSortKey" title="点击此键进行排序">{this.props.content}</th>
);
}
})

这样就可以构造出一个Thead组件了,它的UI表现就是一个表头的 th 元素。一个组件的数据内容由该组件的父级传递过来,父级传递给子级的数据是通过 props 对象来传递的。然后子级组件通过this.props来读取数据对象的内容。父级组件传递数据对象给子级组件的方法也很简单,只要在组件传入一个属性值即可。在这里,TheadCell的父级组件为Thead,创建它并传递数据给TheadCell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var Thead = React.createClass({
render: function (){
return (
<thead>
<caption>点击表格头部进行排序</caption>
<tr>
<TheadCell content={this.props.uid} />
<TheadCell content={this.props.name} />
<TheadCell content={this.props.tel} />
</tr>
</thead>
);
}
});

这样,Thead包含了三个子组件TheadCell,然后类似XML的标签语法定义组件的“属性”——content。在这里,content的值来自Thead的父级组件TableComponent。
同样的道理,创建剩下的所有静态组件:

创建Cell:

1
2
3
4
5
6
7
8
9
10
11
var Cell = React.createClass({
render: function(){
return (
<tr>
<td>{this.props.userItem.uid}</td>
<td>{this.props.userItem.name}</td>
<td>{this.props.userItem.tel}</td>
</tr>
);
}
});

创建Tbody:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Tbody = React.createClass({
render: function (){
var row = [],
userlist = this.props.items;
for(var item in userlist){
row.push(<Cell userItem={this.props.items[item]} key={item}/>);
}
return(
<tbody>
{row}
</tbody>
);
}
});

创建TableComponent:

1
2
3
4
5
6
7
8
9
10
var TableComponent = React.createClass({
render: function (){
return (
<table>
<Thead uid="uid" name="name" tel="tel"/>
<Tbody items={this.props.data}/>
</table>
);
}
});

在这个地方,为了方便,我省略了从服务器加载数据的步骤,直接声明一个静态数据:

1
2
3
4
5
6
var USERS = [
{"uid":4,"name":"zhut","tel":"13899994567"},
{"uid":2,"name":"xiaom","tel":"13580889999"},
{"uid":3,"name":"Brand","tel":"15918446402"},
{"uid":1,"name":"defan","tel":"18913145678"}
];

最后,把组件渲染到html页面上。静态组件也就绘制完成了:

1
React.render(<TableComponent data={USERS} />,document.getElementById("content"));

第三步:识别UI的state

state是组件私有的,可以通过调用 this.setState() 来改变它。当状态更新之后,组件重新渲染自己。也就是说每当state改变之后,React的虚拟DOM机制就会感知到变化然后重新绘制UI。由于虚拟DOM操作是在内存里面,这就使得UI的重新绘制非常地高效快捷。这是React的精髓所在。
好了,寻找state的三步法:

  • 是否是从父级通过 props 传入的?如果是,可能不是 state 。
  • 是否会随着时间改变?如果不是,可能不是 state 。
  • 能根据组件中其它 state 数据或者 props 计算出来吗?如果是,就不是 state 。

在这里,我们的组件是随着TheadCell被点击,整个组件重新排序。所以Thead组件的变化必须影响到Tbody的状态。所以我们把state设置在Thead和Tbody的父级组件TableComponent上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var TableComponent = React.createClass({
getInitialState: function (){
return {
sortBy:''
};
},
render: function (){
return (
<table>
<Thead uid="uid" name="name" tel="tel"/>
<Tbody items={this.props.data} sortBy={this.state.sortBy}/>
</table>
);
}
});

getInitialState 初始化组件的state,然后传递给子级Tbody。

第四步:添加反向数据流

state的动态变化才会导致组件重新绘制,所以发生在组件点击事件必须要能改变组件的state,组件树中层级很深的表单组件TheadCell需要更新 TableComponent 中的 state 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var TableComponent = React.createClass({
getInitialState: function (){
return {
sortBy:''
};
},
handleClick: function(value){
this.setState({
sortBy:value
});
},
render: function (){
return (
<table>
<Thead sort={this.handleClick} uid="uid" name="name" tel="tel"/>
<Tbody items={this.props.data} sortBy={this.state.sortBy}/>
</table>
);
}
});

这里,传递给子级组件Thead一个回调函数handleClick,回调函数触发时调用this.setState()来改变组件的state。由于点击事件是绑定在Thead的子级TheadCell上的,所以我这里的处理方法就是让Thead把回调函数handleClick再传递给下一子级TheadCell。Thead是这样子处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Thead = React.createClass({
returnSort: function(v){
this.props.sort(v);
},
render: function (){
return (
<thead>
<caption>点击表格头部进行排序</caption>
<tr>
<TheadCell bubbleClick={this.returnSort} content={this.props.uid} />
<TheadCell bubbleClick={this.returnSort} content={this.props.name} />
<TheadCell bubbleClick={this.returnSort} content={this.props.tel} />
</tr>
</thead>
);
}
});

把TheadCell的回调作为回调sort的一个过渡。

最后,需要知道鼠标点击的是哪一个表头字段(uid,name还是tel)。React提供的获取标签值得方法是refs,所以 th 标签需要知道一个ref属性,我把它的值叫做getSortKey,也就是用来排序的字段。通过getDOMNode获取到UI的具体节点。在这里需要说明的一点就是,React里面给标签对象注册事件必须使用基于元素的DOM0级事件注册,而且是用驼峰格式。比如注册点击事件onClick。

1
2
3
4
5
6
7
8
9
10
var TheadCell = React.createClass({
handler: function(){
this.props.bubbleClick(this.refs.getSortKey.getDOMNode().innerHTML);
},
render: function (){
return (
<th ref="getSortKey" title="点击此键进行排序" onClick={this.handler}>{this.props.content}</th>
);
}
})

接下来就是双向数据流绑定的重要一步,也就是组件state改变之后UI如何实时响应并重新渲染绘制。这一步是发生在Tbody组件上面的,关键的地方在于表格tbody的绘制必须要由state计算而来,这样state的改变才会导致UI的重新绘制:

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
var Tbody = React.createClass({
render: function (){
var row = [],
userlist = this.props.items,
cmpKey = "",
sortBy = this.props.sortBy;
// 获取需要拿来比较的字段
for (it in userlist[0]){
if(sortBy == it){
cmpKey = it;
}
}
// 如果有排序键,进行排序
if (cmpKey){
userlist.sort(function (a,b){
if(a[cmpKey] < b[cmpKey]){
return -1;
}
if(a[cmpKey] > b[cmpKey]){
return 1;
}
return 0;
});
}
// 排序后的table
for(var item in userlist){
row.push(<Cell userItem={this.props.items[item]} key={item}/>);
}
return(
<tbody>
{row}
</tbody>
);
}
});

本篇结语

这一篇主要是介绍怎么样去使用React制作组件,这是入门级别的说明。想要更多地了解React.js的朋友可以进入官方文档进行深入学习。