文章目录
  1. 1. ReactJS 简介
    1. 1.1. ReactJS的背景和原理
    2. 1.2. 组件化
  2. 2. HTML 模板
  3. 3. ReactDOM.render()
  4. 4. JSX 语法
  5. 5. 组件
  6. 6. PropTypes
  7. 7. 获取真实的DOM节点
  8. 8. this.state
  9. 9. 表单
  10. 10. 组件的生命周期
  11. 11. Ajax

ReactJS 简介

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。

ReactJS官网地址:http://facebook.github.io/react/

Github地址:https://github.com/facebook/react

ReactJS的背景和原理

在Web开发中,我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因。React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用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的。

组件化

虚拟DOM(virtual-dom)不仅带来了简单的UI开发逻辑,同时也带来了组件化开发的思想,所谓组件,即封装起来的具有独立功能的UI部件。React推荐以组件的方式去重新思考UI构成,将UI上每一个功能相对独立的模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成大的组件,最终完成整体UI的构建。例如,Facebook的instagram.com整站都采用了React来开发,整个页面就是一个大的组件,其中包含了嵌套的大量其它组件。

HTML 模板

使用 React 的网页源码,结构大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<script src="../build/react.js"></script>
<script src="../build/react-dom.js"></script>
<script src="../build/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
// ** Our code goes here! **
</script>
</body>
</html>

上面代码有两个地方需要注意。首先,最后一个 <script> 标签的 type 属性为 text/babel 。这是因为 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"

其次,上面代码一共用了三个库: react.jsreact-dom.jsBrowser.js ,它们必须首先加载。其中,react.js 是 React 的核心库,react-dom.js 是提供与 DOM 相关的功能,Browser.js 的作用是将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。

ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点

1
2
3
4
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);

上面代码将一个 h1 标题,插入 example 节点(查看 demo01

JSX 语法

HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写:

1
2
3
4
5
6
7
8
9
10
11
12
var names = ['Alice', 'Emily', 'Kate'];

ReactDOM.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
);

上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析(查看 Demo02

JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员

1
2
3
4
5
6
7
8
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);

上面代码的arr变量是一个数组,结果 JSX 会把它的所有成员,添加到模板,运行结果(查看 demo03

组件

React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类

1
2
3
4
5
6
7
8
9
10
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.name}</h1>;
}
});

ReactDOM.render(
<HelloMessage name="John" />,
document.getElementById('example')
);

上面代码中,变量 HelloMessage 就是一个组件类。模板插入 <HelloMessage /> 时,会自动生成 HelloMessage 的一个实例(下文的”组件”都指组件类的实例)。所有组件类都必须有自己的 render 方法,用于输出组件。

注意,组件类的第一个字母必须大写,否则会报错,比如HelloMessage不能写成helloMessage。另外,组件类只能包含一个顶层标签,否则也会报错。

1
2
3
4
5
6
7
8
9
var HelloMessage = React.createClass({
render: function() {
return <h1>
Hello {this.props.name}
</h1><p>
some text
</p>;
}
});

上面代码会报错,因为HelloMessage组件包含了两个顶层标签:h1p

组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如 <HelloMessage name="John">,就是 HelloMessage 组件加入一个 name 属性,值为 John。组件的属性可以在组件类的 this.props 对象上获取,比如 name 属性就可以通过 this.props.name 读取(查看 Demo04

添加组件属性,有一个地方需要注意,就是 class 属性需要写成 classNamefor 属性需要写成 htmlFor ,这是因为 classforJavaScript 的保留字。

PropTypes

组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。

组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求(查看 demo06

1
2
3
4
5
6
7
8
9
var MyTitle = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
},

render: function() {
return <h1> {this.props.title} </h1>;
}
});

上面的Mytitle组件有一个title属性。PropTypes 告诉 React,这个 title 属性是必须的,而且它的值必须是字符串。现在,我们设置 title 属性的值是一个数值。

1
2
3
4
5
6
var data = 123;

ReactDOM.render(
<MyTitle title={data} />,
document.body
);

这样一来,title属性就通不过验证了。控制台会显示一行错误信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes设置,可以查看官方文档

此外,getDefaultProps 方法可以用来设置组件属性的默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var MyTitle = React.createClass({
getDefaultProps : function () {
return {
title : 'Hello World'
};
},

render: function() {
return <h1> {this.props.title} </h1>;
}
});

ReactDOM.render(
<MyTitle />,
document.body
);

上面代码会输出”Hello World”。

获取真实的DOM节点

组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。

但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref 属性(查看 demo07

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});

ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);

上面代码中,组件 MyComponent 的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref 属性,然后 this.refs.[refName] 就会返回这个真实的 DOM 节点。

需要注意的是,由于 this.refs.[refName] 属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会读取 this.refs.[refName] 属性。

React 组件支持很多事件,除了 Click 事件以外,还有 KeyDownCopyScroll 等,完整的事件清单请查看官方文档。

this.state

组件免不了要与用户互动,React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI (查看 demo08

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var 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>
);
}
});

ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);

上面代码是一个 LikeButton 组件,它的 getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

由于 this.propsthis.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。

表单

用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props 读取(查看 demo9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Input = React.createClass({
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function () {
var value = this.state.value;
return (
<div>
<input type="text" value={value} onChange={this.handleChange} />
<p>{value}</p>
</div>
);
}
});

ReactDOM.render(<Input/>, document.body);

上面代码中,文本输入框的值,不能用 this.props.value 读取,而要定义一个 onChange 事件的回调函数,通过 event.target.value 读取用户输入的值。textarea 元素、select元素、radio元素都属于这种情况,更多介绍请参考官方文档

组件的生命周期

组件的生命周期分成三个状态:

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。

  • componentWillMount()
  • componentDidMount()
  • componentWillUpdate(object nextProps, object nextState)
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount()

此外,React 还提供两种特殊状态的处理函数。

  • componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
  • shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用

这些方法的详细说明,可以参考官方文档

Ajax

组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount 方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法重新渲染 UI (查看 demo11

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
var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},

componentDidMount: function() {
$.get(this.props.source, function(result) {
var lastGist = result[0];
if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
},

render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});

ReactDOM.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
document.body
);

上面代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 本身没有任何依赖,完全可以不用jQuery,而使用其他库。

我们甚至可以把一个Promise对象传入组件,查看Demo12

1
2
3
4
5
6
ReactDOM.render(
<RepoList
promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
/>,
document.body
);

上面代码从Github的API抓取数据,然后将Promise对象作为属性,传给RepoList组件。

如果Promise对象正在抓取数据(pending状态),组件显示”正在加载”;如果Promise对象报错(rejected状态),组件显示报错信息;如果Promise对象抓取数据成功(fulfilled状态),组件显示获取的数据。

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
var RepoList = React.createClass({
getInitialState: function() {
return { loading: true, error: null, data: null};
},

componentDidMount() {
this.props.promise.then(
value => this.setState({loading: false, data: value}),
error => this.setState({loading: false, error: error}));
},

render: function() {
if (this.state.loading) {
return <span>Loading...</span>;
}
else if (this.state.error !== null) {
return <span>Error: {this.state.error.message}</span>;
}
else {
var repos = this.state.data.items;
var repoList = repos.map(function (repo) {
return (
<li>
<a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}
</li>
);
});
return (
<main>
<h1>Most Popular JavaScript Projects in Github</h1>
<ol>{repoList}</ol>
</main>
);
}
}
});
文章目录
  1. 1. ReactJS 简介
    1. 1.1. ReactJS的背景和原理
    2. 1.2. 组件化
  2. 2. HTML 模板
  3. 3. ReactDOM.render()
  4. 4. JSX 语法
  5. 5. 组件
  6. 6. PropTypes
  7. 7. 获取真实的DOM节点
  8. 8. this.state
  9. 9. 表单
  10. 10. 组件的生命周期
  11. 11. Ajax