React(二) 阮一峰React入门

阮一峰博主通过React实例教程演示React入门

大纲

Demo1——HelloWorld

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!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">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
</body>
</html>

这是我们构建的第一个React项目——HelloWorld,让我们一步一步来解析这个项目:

  1. 引入React库文件:首先通过在中引入三个JS文件脚本,来将React相关库加载进来,react.js是React的核心库,react-dom.js是React提供DOM操作的库,browser.js是将JSX语法转化为JS语法的库
  2. JSX语法:React采用的JSX语法需要经过babel解析才能被转换成JS被浏览器所解析,因此在书写JSX语法的地方需要使用

    1
    <script type="text/babel"></script>

    标签声明,浏览器才能在渲染网页时识别出JSX语法并调用babel解析器将其转换成JS语法

  3. ReactDOM.render():是React最基本的方法,用于将模板转为HTML语言,并插入指定的DOM节点中,注意这里面是有两步操作对应render()方法的两个参数,第一个参数是模板,里面可以将HTML语言直接写在JS语言中,第二参数是DOM节点,用于将第一个参数插入到指定的DOM节点中

运行HelloWorld文件可看到

成功在id为example的div节点中插入了HelloWorld标签

Demo2——JSX解析模板

源码

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
<!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="example1"></div>
<div id="example2"></div>
<script type="text/babel">
var names = ['Alice', 'Emily', 'Kate'];

//对于JS数组,可用函数循环返回
ReactDOM.render(
<div>
{
names.map(function (name, index) {
return <h1 key={index}>Hello, {name}!</h1>
})
}
</div>,
document.getElementById('example1')
);

var arr = [
<h1 key="1">Hello world!</h1>,
<h2 key="2">React is awesome</h2>,
];

//对于JSX数组,可直接调用,JSX会将数组全部展开
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example2')
);
</script>
</body>
</html>

上述是JSX解析模板语法的实例,可以看出

  1. JSX解析模板规则:最外层为一个div标签,因此遇到HTML标签,使用HEML标签规则解析,遇到{}代码块,使用JS规则解析,在{}代码块内可用JS任何语法,如循环遍历数组等
  2. 模板内插入JS变量:若变量为一个数组,则JSX会展开数组的所有成员

运行上述代码,如下

循环遍历出了数组元素,并且展开了数组所有成员

Demo3——组件

组件:React允许将代码封装成组件(component),然后像插入普通HTML标签一样,在网页中插入这个组件
源码

1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/babel">
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.name},{this.props.msg}</h1>;
}
});

ReactDOM.render(
<HelloMessage name="John" msg="Nice to meet you!" />,
document.getElementById('example')
);
</script>

上面就是在JSX中自定义组件和将组件插入到DOM中了,注意其中的细节:

  1. 声明组件名必须以大写字母开头
  2. 组件内只能包含一个顶层标签,不能出现顶层并列多个标签
  3. 组件必须重写render方法,用于输出组件
  4. 在组件内部可使用{this.props.属性名}获取组件属性的value

运行上述代码,如下

可以看到将自定义的HelloMessage组件插入到DOM标签中了

Demo4——组件特殊属性【this.props.children】

this.props对象属性与组件属性一一对应,但this.props.children属性表示组件所有子节点
源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="text/babel">
var NotesList = React.createClass({
render: function() {
return (
<ol>
{
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
}
</ol>
);
}
});

ReactDOM.render(
<NotesList>
<span>hello</span>
<span>world</span>
</NotesList>,
document.getElementById('example')
);
</script>

处理子节点有几点需要注意:

  1. this.props.children返回值:组件没有子节点返回undefined,一个自己子节点返回object,多个子节点返回array
  2. React.Children提供方法处理:如使用React.Children.map遍历数组,使用React.Children.forEach遍历数组但不返回,使用React.Children.count返回子节点数量,使用React.Children.only验证是否只有一个子节点并返回

运行上述代码,如下

可以看到有两个子节点并将他们遍历放在li便签中输出

Demo5——组件属性方法

对于获取到的组件属性,我们可以使用PropTypes验证其是否符合要求,可以使用getDefaultProps设置组件默认属性
源码

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
<script type="text/babel">

var data = 123;

var MyTitle = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
},

getDefaultProps : function () {
return {
title : 'Hello World',
name : 'Harvie'
};
},
render: function() {
return <div>
<h1> title:{this.props.title} </h1>
<h1> name:{this.props.name} </h1>
</div>;
}
});

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

</script>

上述分别使用了PropTypes和getDefaultProps方法

  1. 使用PropTypes验证了title属性是否为string
  2. 使用getDefaultProps规定了默认name属性值为Harvie

运行上述代码,如下

可以看到没有添加name属性但是还是输出了默认name属性,title属性不是string但还是显示出来了,查看控制台

可以看到控制台发生报错,修改为string后可取消报错

Demo6——this.refs.refName获取真实DOM

组件不是真实的DOM节点,是存在于内存中的虚拟DOM,只有插入文档中才会变成真实DOM
React规定,所有DOM变化,都先在虚拟DOM上变化,之后再放映在真实DOM,可以极大提高网页性能
源码

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

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

上述代码实现了React组件中的点击事件,注意以下细节:

  1. 使用ref属性从组件获取真实DOM节点:虚拟DOM中获取不到输入数据,需要在text标签中绑定ref属性,通过this.ref.[refName]获取真实DOM节点,获取真实DOM节点后就像JS中操作DOM节点了,如其value、innerHTML、attribute属性等
  2. React事件:通过在组件中声明点击回调函数(如handleClick),在HTML中指定onClick指定点击回调函数,来实现Click点击事件,除了Click事件后还有许多事件,如KeyDown 、Copy、Scroll等

运行上述代码,如下

可以看到,输入文本Harvie后,点击按钮,触发handleClick函数获取input标签的text内容填充到h1标签中

Demo7——this.state

props是组件中的不变特性,state是组件中一个可变的变量,随着与用户互动组件状态值会改变,每次改变都会重新渲染UI
源码

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')
);

上述代码定义了一个通过点击状态改变并显示出来的组件,注意以下细节:

  1. getInitialState方法:用于定义初始状态对象,这个对象可通过this.state读取
  2. this.setState方法:用于改变状态值,通过(stateName:value)修改状态值,每次修改后会自动调用this.render方法重新渲染组件

运行上述代码,如下

点击文字后

可以看到点击后调用点击函数内的this.setState函数重新设置状态后重新调用this.render方法重新渲染组件

Demo8——event.target.value获取输入

用户在表单、选项、选择等输入,属于用户跟组件互动,所以不能通过this.props读取,应该使用event.target.value读取
源码

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);

上述代码定义了一个获取输入并显示出来的组件,注意以下细节:

  1. onChange:在输入框中的文本改变时,会调用onChange()指定的回调函数
  2. event.target.value:用于读取触发事件的DOM的value值

运行上述代码,如下

可以看到改变输入框内容改变下面的p标签的内容也会改变

Demo9——组件的生命周期函数

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

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

React为每种状态提供两种处理函数:will函数在进入状态前调用,did函数在进入函数后调用

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

此外还有两种特殊状态处理函数:

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

源码

上述代码定义了一个不透明度不断变化的HelloWorld标签,注意以下细节:

  1. 首先通过getInitialState设置初始不透明状态为1.0
  2. 通过componentDidMount函数:在组件插入真实DOM结束后调用定时器改变组件不透明度状态重新渲染组件
  3. setInterval(callback function,milliseconds)函数:在指定milliseconds毫秒数不断执行callback function回调函数,在这里回调函数采用匿名函数,主要是获取当前不透明状态值、-0.05、如果小于0.1就重置为1.0、以新的不透明状态值重新渲染UI,秒数设为100ms
  4. React组件样式:是一个对象,因此要写成,第一个大括号表示JS语法域,第二个大括号表示这是一个样式对象

运行上述代码,如下



出现一个不透明度不断变化的HelloWorld标签

Demo10——组件数据来源AJAX

可以通过在componentDidMount函数里面通过AJAX请求数据后通过this.setState改变状态重新渲染组件
源码

1
2
3
4
5
6
7
8
9
10
11
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));
}

上述代码是使用jQuery完成AJAX请求后使用this.setState改变组件状态重新渲染组件

Promise对象表示先执行一个任务,根据这个任务结果来决定是否执行下个任务,可以使用.then来实现
可以将Promise对象作为属性传给组件,然后组件根据Promise执行首次任务的结果改变组件自身状态重新渲染组件,从而达到加载的效果
源码

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

//组件插入真实DOM后启动函数
componentDidMount() {
//通过promise.then获取执行AJAX的结果
//若请求成功,则修改loading为false,data为所得到的数据
//若请求失败,则修改loading为false,error为错误信息
this.props.promise.then(
value => this.setState({loading: false, data: value}),
error => this.setState({loading: false, error: error}));
},

//渲染组件
render: function() {
//若是处于loading状态,则显示loading
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>
);
}
}
});
//用AJAX请求api获取到的json数据存在promise对象中
//然后将promise对象以属性的传给组件
ReactDOM.render(
<RepoList
promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
/>,
document.body
);