React——带你走进组件化的世界

Facebook一个开源项目掀起了前端界的惊涛骇浪,React到底是何方神圣,现在就让我们一起走进React的世界。

React是什么?

React起源

React最初来自Facebook内部的广告系统项目,项目实施过程中前端开发遇到了巨大挑战,代码变得越来越臃肿且混乱不堪,难以维护。于是痛定思痛,他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,于是就有了React。创造 React 是为了解决一个问题:构建随着时间数据不断变化的大规模应用程序。

React的误区

  • React并不是一个MVC的框架,最多可以认为是MVC中的V(View),说它是一个JS类库可能更加贴切。
  • React跟MVVM框架,像angular那些有区别,数据是单向流动的,仅仅只会从model往view流动,而不会倒流。
  • React并不是一个新的模板语言,jsx并不是必须的,没有jsx的React也能工作。

    Why React

    框架或者类库可以极大提高我们的生产力,但我们更加要清楚我们为什么要用它,它对比起其他的框架有什么优越的地方。

    Virtual Dom

    在了解Virtual Dom之前,我们来巩固一个知识,我们的浏览器是有Repaint和Reflow操作的,当元素的外观改变,但不改变Dom结构的时候,浏览器会发生Repaint,如background color;但当Dom结构发生改变的时候,如width heiht改变影响布局时,会发生Reflow。浏览器执行js的速度是非常快的,主要性能消耗就在于Reflow上,而React就是使用Virtual Dom来解决频繁操作Dom引起Reflow而导致的性能问题。
    在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A变成B,然后又从B变成A,React会认为UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是Diff部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
    来看个简单的例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Before : 
    <div>
    <p>hello world </p>
    <span>Hello</span>
    </div>
    After :
    <div>
    <span>hello world </span>
    <span>Hello</span>
    </div>

tmp2e0d2de6.png
React在内存里维护的完整的Dom,然后当发生改变的时候(这里p标签变成了span标签),React会用diff算法去计算两棵Dom的不同的地方,然后统一更改,引发一次的Reflow将更改体现在页面上。关于Diff算法,在后续的文章中会深入讲解。

走进React的世界

首先,我们先来感受下React最简单的用法:

1
2
3
4
5
6
7
8
9
10
var React = require('react');
var ReactDom = require('react-dom');
var Hello = React.createClass({
render : function(){
return (
<div>HelloWorld</div>
);
}
});
ReactDom.render(<Hello/>,document.getElementById('content'));

这是jsx的写法,等下再详细介绍jsx,如果需要运行的话需要先解析jsx的语法,目前我所知道的有三种方法:

  • 引用cdn的react和babel的转换js(不推荐)
  • 在服务器端先将jsx转换成js
  • 使用webpack等构建工具(推荐)

如上面的helloworld所示,我们将div封装成Hello标签,这样下次我们使用的时候就可以直接用Hello标签,这也是我们说React带来的组件化思想。这里需要注意的是,render返回的必须用一个块级元素包裹着,不然会报错。

jsx

jsx是一个看起来很像 XML 的 JavaScript 语法扩展。你不需要为了 React 使用 JSX,可以直接使用纯粹的 JS。但我们更建议使用 JSX , 因为它能定义简洁且我们熟知的包含属性的树状结构语法。再强调一次,jsx并不是使用react必须的!!!下面我们分别来看看用js和jsx实现helloworld有什么异同点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jsx:
var React = require('react');
var ReactDom = require('react-dom');
var Hello = React.createClass({
render : function(){
return (
<div>HelloWorld</div>
);
}
});
ReactDom.render(<Hello/>,document.getElementById('content'));

js:
var React = require('react');
var ReactDom = require('react-dom');
var Hello = React.createClass({displayName: "Hello",
render : function(){
return (
React.createElement("div", null, "HelloWorld")
);
}
});
ReactDom.render(React.createElement(Hello, null),document.getElementById('content'));

我们会发现,jsx会比js更加简洁,并且容易像html那样容易读懂。

React常用属性

下面让我们通过一些例子了解React的常用属性,从而更好理解React的用法。接下来我都会用jsx来实现。

Props属性

Props是从父级获取数据的一个属性,可以从父级组件传递数据到子级组件。让我们来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var React = require('react');
var ReactDOM = require('react-dom');
var Hello = React.createClass({
render : function(){
return (
<div>
{this.props.name}
</div>
);
}
});
ReactDOM.render(
<Hello name="Helloworld"/>,
document.getElementById('content')
);

这个简单的例子我们可以看到,我们从父级传递name属性到Hello标签,然后在Hello标签内部使用props来取到name属性。

事件机制

React的事件机制与我们最原始的时间机制相似,直接挂在元素上,看看下面的例子可以很好明白:

1
2
3
4
5
6
7
8
9
10
11
12
13
var React = require('react');
var ReactDom = require('react-dom');
var Hello = React.createClass({
handleClick:function(){
alert('I am helloworld');
},
render : function(){
return (
<div onClick={this.handleClick}>HelloWorld</div>
);
}
});
ReactDom.render(<Hello/>,document.getElementById('content'));

对于各类型的事件可以在官网查询。

state属性——状态机

在之前做SPA的时候,我遇到了一个困惑,就是维护用户的状态很麻烦,因为很多页面有很多共同点,仅仅可能因为状态的不同而需要修改其中的一些小地方,当项目足够复杂的时候,人也就跟着难受了。然后React的state让我不再关心状态改变的时候页面应该如何随着改变,只需要关心某个状态下页面长什么样子就好了,剩下的React帮你解决了。我们先来看看简单的例子:

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
49
50
51
52
53
54
55
56
57
58
59
var React = require('react');
var ReactDOM = require('react-dom');
var Fliter = React.createClass({
getInitialState:function(){
return {
show:false
};
},
handleChange:function(){
this.setState({
show : this.refs.mycheck.checked
})
},
render : function(){
return (
<div>
<input type="checkbox" onChange={this.handleChange} ref="mycheck"/>
<List show={this.state.show} list={this.props.list} />
</div>
)
}
});
var Item = React.createClass({
render : function(){
return (
<div>{this.props.name}</div>
)
}
});
var List = React.createClass({
render:function(){
var tmp = [],
show = this.props.show;
this.props.list.forEach(function(item){
if(item.show==show){
tmp.push(<Item name={item.name}/>);
}
});
return (
<div>
{tmp}
</div>
);
}
});

var data = [
{'name':'blue',"show":true},
{'name':'pink',"show":true},
{'name':'red',"show":true},
{'name':'white',"show":false},
{'name':'black',"show":false},
{'name':'yellow',"show":true}
];

ReactDOM.render(
<Fliter list={data}/>,
document.getElementById('content') //不要加分号!!!
);

state最主要的就是两个函数,一个是getInitialState,设置状态的初始值,另外一个就是setState,改变状态,这时候会调用render重新渲染页面。state难点就在于应该将什么数据定义为state,并且在什么地方定义才能统一管理页面,当对这两个了解清楚的时候,对于React组件的定义就轻门熟路了。

结语

这篇文章只是对于React入门的一些介绍,对于React还有diff算法、数据流的flux、原生结合的React Native这些大块的内容,后面将一一学习并且总结。