React实践系列一 JSX

关于React

React最近很火。一来一直以来DOM操作都是性能瓶颈,二来从另一个角度来诠释模块化。虽然说它按传统MVC的概念来分的话智仅仅能算V层面,同AngularJS这类MVVM框架在广度有较大距离。但是呢,一件事物不能一味求大求全,何况并不是一味大就可以解决EveryThing————比如YUI。

React之所以成功,我以为,它是击中了前端从业人员的痛点:

  1. 多个文章和书籍都无数次在说,DOM是性能后腿。然而避无可避。
  2. 模块化说来容易做起来永远是一项业界痛点。太多代码写了一年又一年,也许下一年还会重复写。

React根据这两个痛点做了两件事:

  1. shadow DOM ==> 减少不必要的DOM操作
  2. JSX ==> 将模块相关的HTML,CSS,JS放到一起,这也许是一项前端领域的“逆生长”。

——本文只说JSX

JSX

JSX有什么好处呢:

  • 允许使用熟悉的语法来定义HTML元素树
  • 提供更加语义化且易懂的标签
  • 程序结构更加直观
  • 抽象了React.Element的创建过程
  • 随时掌控HTML标签以及生成这些标签的代码
  • 原生的Javascript

当然,上面说了那么多只是文科考试一般的答案,实际工作中,没人会背诵下来。实际上,JSX仅仅是发明出来方便将html,css,js杂糅到一起做成模块用的工具,用不用可以看喜好。简单来对比下用和不用的区别(当然,用不用JSX,React依然是React):

1
2
3
4
//不使用JSX
React.createElement('h1',{className:"aaa"},"hello world");
//使用JSX
<h1 class="aaa">hello world</h1>

两者在最后运行时候是等效的,但是很显然,JSX抹平了HTML在js中的转换问题,做到了将html标签转换js可识别的DOM对象的自动化——大家都干过用N个”+”来拼接HTML字符串并innerHTML到dom元素中的活儿,一旦字符串庞大,这个拼接就会很费神,然而这仅仅是字符串,手动来操作一个复杂的html字符串转换js认识的DOM,这将会更加复杂。所幸JSX为我们做好了这项工作。

step1

首先让我们先来个Hello World!第一步就老实从Hello组件做起好了。来个半官方的demo,我仅仅改了标签而已。注意这里JSX浏览器端编译工具体积达到1.3M,这里再次说明了,这个转换是一件相当复杂的事情。

JS Bin on jsbin.com

step2

hello world到此为止就可以运行了,但是这个demo仅仅是一个写死的,不可以复用的html——事实上复制粘贴比它快多了,对吧?
第二步,来构建一个简单的组件。这里我抄个demo过来,就不写了。


组件的层级:

  • CommentBox
    • CommentList
      • Comment
    • CommentForm

      代码如下:

JS Bin on jsbin.com

这里定义一个评论组件,他包含一个评论列表和一个提交评论的表单,我们用React.createClass分别构造了这三个小模块,然后在CommentBox中引用了CommentList和CommentForm,这样,一个包含列表和表单的评论组件就做好了——当然,它只是随便填充了点文字意思了那么一下。。。

step3

到此为止,这个组件可以实现复用了,但是,它的数据还是写死的。CommentList里面也没有Comment,完善一下:
JS Bin on jsbin.com

说一下这里有哪些需要注意的,它做了什么

  • 添加了一个Comment组件,并改变了CommentList
  • 传入了data对象
  • 在CommentList中根据传入的data遍历输出了Comment

写在step4之前

写到step3,步子跨越的有些大了。这里补充一下关于JSX的一些基础,和语法相关常识。

JSX的注释

JSX本质上是js,即使当它是JSX时候可以不太遵循JS语法,但是一旦编译成JS,它就必须遵循JS的语法。

这里简单说一下常见的两种情况:

  • 注释在JS中

  • 注释在HTML中

注释在JS中

注释在JS中其实没有太多好说的,和常规的JS一样

注释在HTML中

注释在HTML中的话,有两种情况:

  1. 作为子节点
  2. 作为内联属性
1
2
3
4
5
6
7
8
9
10
11
12
//作为子节点
<div>
{
//这是一个注释
}
<h1>helloWorld</h1>
</div>

//作为内联属性
<input
//这是一个注释
type="text">

这里的注释,可以使用 “//“&”/* */“

JSX模板替代性相关

模板的作用众所周知,它至少起到以下作用:

  1. 在标记中输出变量
  2. 选择性输出HTML
  3. 遍历输出固定HTML标记
  4. 分支支持(if else之流)

这里就根据上面4个,一一说到JSX中的替代解决方案

在标记中输出变量 –> {}

在JSX中,花括号{}中间的内容会被渲染为动态值,在{}中放入的任何东西都会被求值,实际上每个{}之间,都会放入一个context(上下文)。

1
2
3
4
5
6
7
8
9
10
11
//解析变量
var test = "helloWorld";
<h2>{test}</h2> //==><h2>helloWorld</h2>
//执行函数
function hello(a,b){
return String(a)+String(b);
}
<h2>{hello("hello","World")}</h2> //==><h2>helloWorld</h2>
//解析数组
var test = [1,2,3]
<h2>{test}</h2> //==><h2>123</h2>

选择性输出HTML –> dangerouslySetInnerHTML
1
2
3
4
5
6
7
8
9
10
...
function createMarkup() {
return {
__html: '<span>Hello World</span>'
};
...
render:function(){
return <div dangerouslySetInnerHTML={createMarkup()} />
}
...
遍历输出固定HTML标记 –> this.props.data.map

关于遍历输出固定html标记,在上文的例子中有一个已经运行起来的demo,把它提取出来:

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
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment author={comment.author}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});

大致说一下这里出现的各种变量:
首先要说的是 {this.props.children} ,这是一个非常关键的变量。它指向哪个变量呢?

1
2
3
4
5
6
7
8
9
10
11
//在CommentList中调用
<Comment author=张三>
测试变量
</Comment>
//最后在编译成的:
<div class="comment">
<h2 class="commentAuthor">
张三
</h2>
测试变量
</div>

对比上面两个code片段,可以看到:{this.props.children}的地方,出现了”测试变量”。

——{this.props.children}实际上就是指向了Comment标记开始到结尾过程中的子元素

这里解释了JSX中如何生成遍历过程中输出不同变量的,但是还没有说明如何进行遍历过程,这里就是this.props.data.map的作用了。熟悉JS的话就不需要再解释了,map对数组每个元素操作后返回了新的变量组成的数据,最后放到{}中去解析去了:数组是会被当做字符串加起来innerHTML到目标位置的。

最后整理下实现的路径:

  1. 定义Comment组件,把Comment标记中的文本作为变量放到内容区,以备遍历过程中输出不同变量之需
  2. 使用map方法生成一个包含多个Comment组件的数组,把逻辑放到CommentList定义语句中
  3. 将返回的数组放到{}中去解析生成,从而遍历生成子节点
分支支持(if else之流)

分支这个最后决定一笔略过了——前面说过了,{}中会有个context,所以,if/else在js中怎么用,这里就可以怎么用。

总结

JSX应该算是是React的语法糖,它是React的语法更加简洁,好写和好认。用好JSX是React实现实用意义的第一步。
本文简单解释了必要的概念,从step1到step4,实现了一个非常简单评论组件——虽然它很简单,但是一旦封装好却可以重复使用。
在step1到step4,实践了复用子组件的同时,实现了模板在实用意义中至关重要的3点:

  1. 在标记中输出变量
  2. 选择性输出HTML
  3. 分支支持(if else之流)

本文将就此终止,但是本文仍不完善,比如没有讲到this.props,但是这部重要,最重要的是,如何把JSX当成模板用一回,是将JSX作为实用工具最初的一步。关于this.props和组件中数据传递,下一篇再说。

this end