React入门

介绍

课程地址:阿里云React

课程PPT:React入门

课程代码:Github

官方文档:安装

运行准备

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React系列教程:从入门到实战</title>
<script src="https://a.alipayobjects.com/??es5-shim/4.0.5/es5-shim.js,es5-shim/4.0.5/es5-sham.js,html5shiv/3.7.2/src/html5shiv.js,react/0.13.3/react.js,react/0.13.3/JSXTransformer.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/jsx"></script>
</body>
</html>

定义

具体是做UI组件的库,专注于MVC中的view

用状态控制组件变化

一个组件作为一个状态机,每一个状态对应组件中的一个UI

作用

  1. API少
  2. 组件内聚
  3. 原生组件和自定义组件融合渲染
  4. 状态/属性驱动全局更新
  5. commonjs生态圈/工具联链完善

基础知识

JSX

描述组件树

必须驼峰命名

属性名不能和js关键字冲突

1
2
3
4
<div className="x">
<a href="#">#</a>
<Component x="y">1</Component>
</div>
1
React.createElement('div',{className:'x'},[React.createElement('a',{href:'#'},'#'),React.createElement(Component,{x:'y'},1)]);

可以通过{变量名}来将变量的值作为属性值

1
2
3
var x = 'http://www.alipay.com';
var y = <a href={x} target='_blank'>alipay</a>;
React.render(y,document.getElementById('container'));

可以通过 {…obj} 来批量设置一个对象的键值对到组件的属性,注意顺序

1
2
3
4
5
6
7
var x = 'http://www.alipay.com';
var obj = {
href: 'http://www.taobao.com',
target: '_blank'
}
var y = <a {...obj} href={x}>alipay.com</a>;
React.render(y,document.getElementById('container'));

EVENT

可以通过设置原生 dom 组件的 onEventType 属性来监听 dom 事件,在加强组件内聚性的同时,避免了传统 html 的全局变量污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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>)
}
})
React.render(<LikeButton />,document.getElementById('container'));

组合

可以像使用原生 dom 组件一样使用自定义的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
var A = React.createClass({
render(){
return <a href='#'>a</a>
}
})
var B = React.createClass({
render(){
return <i><A />b</i>
}
})
React.render(<B />,document.getElementById('container'));

自定义组件中可以通过this.props.children访问自定义组件的子节点

1
2
3
4
5
6
7
8
9
var B = React.createClass({
render(){
return <ul>{React.Children.map(this.props.children,function (c) {
return <li>{c}</li>
})}</ul>
}
})
React.render(<B><a href='#'>1</a>2</B>,document.getElementById('container'));

PROPS

通过 this.props 可以获取传递给该组件的属性值

通过定义getDefaultProps 来指定默认属性值

通过指定propTypes可以校验属性值的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var B = React.createClass({
propTypes:{
title: React.PropTypes.string
},
getDefaultProps(){
return {
title:'default'
}
},
render(){
return <b>{this.props.title}</b>
}
})
React.render(<div><B title='change'></B><B title='123'></B></div>,document.getElementById('container'))

STATE

组件内部状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Timer = React.createClass({
getInitialState(){
return {
secondsElapsed:0
}
},
tick(){
this.setState({
secondsElapsed: this.state.secondsElapsed + 1
})
},
componentDidMount(){
this.interval = setInterval(this.tick,1000)
},
componentWillUnmount(){
clearInterval(this.interval)
},
render(){
return(<div>Seconds Elapsed: {this.state.secondsElapsed}</div>)
}
})
React.render(<Timer />,document.getElementById('container'));

MIXIN

mixin 是一个普通对象,通过 mixin 可以在不同组件间共享代码

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
var mixin = {
propTypes:{
title: React.PropTypes.string
},
getDefaultProps(){
return {
title:'default'
}
}
}
var A = React.createClass({
mixins:[mixin],
render(){
return <i>{this.props.title}</i>
}
})
var B = React.createClass({
mixins:[mixin],
render(){
return <b>{this.props.title}</b>
}
})
React.render(<div><B /><A title={2} /><A /></div>,document.getElementById('container'))

FORM

  1. value/checked 属性设置后,组件变为受控组件,用户输入无效,除非程序去改变

    1
    2
    3
    4
    5
    6
    7
    var Test = React.createClass({
    render(){
    return <input value='x' />
    }
    })
    React.render(<Test />,document.getElementById('container'))

  1. textarea 的值要设置在 value 属性

  2. select 的 value 属性可以是数组,不建议使用 option 的 selected 属性

  3. input/textarea 的 onChange 用户每次输入都会触发(即使不失去焦点)。通过监听该事件再结合state来改变input的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var Test = React.createClass({
    getInitialState(){
    return {
    value: 'asdf'
    }
    },
    onChange(e){
    this.setState({
    value:e.target.value
    })
    },
    render(){
    return <input value={this.state.value} onChange={this.onChange} />
    }
    })
    React.render(<Test />,document.getElementById('container'))

  1. 设置defaultValue为input的初始值,之后input的值由用户输入,不推荐

    1
    2
    3
    4
    5
    6
    7
    var Test = React.createClass({
    render(){
    return <input defaultValue='x' />
    }
    })
    React.render(<Test />,document.getElementById('container'))

  2. radio/checkbox 点击后触发 onChange

REF

该功能是为了结合现有非 react 类库,通过 ref/refs 可以取得组件实例,进而取得原生节点

1
2
3
4
5
6
7
8
9
10
11
12
13
var Test = React.createClass({
componentDidMount(){
alert(React.findDOMNode(this.refs.abc).innerHTML);
},
render(){
return <div>
<h3>header</h3>
<div ref='abc'>content</div>
</div>;
}
});
React.render(<Test />,document.getElementById('container'));

COMPONENT API

构成API

React.createClass 定义组件时允许传入的配置

  1. getDefaultProps 得到默认属性对象
  2. propTypes 属性检验规则
  3. mixins 组件间公用方法

COMPONENT LIFECYCLE(构成生命周期)

React.createClass 定义时允许传入的函数,会在特定生命周期内调用

初次创建组件时调用

  1. getInitialState 得到初始状态对象
  2. render 返回组件树. 必须设置
  3. componentDidMount 渲染到 dom 树中是调用,只在客户端调用,可用于获取原生节点

组件的属性值改变时调用

  1. componentWillReceiveProps 属性改变调用
  2. shouldComponentUpdate 判断是否需要重新渲染
  3. render 返回组件树. 必须设置
  4. componentDidUpdate 渲染到 dom 树中是调用, 可用于获取原生节点

componentWillUnmount 组件从 dom 销毁前调用

API在不同生命周期内调用

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React系列教程:从入门到实战</title>
<script src="https://a.alipayobjects.com/??es5-shim/4.0.5/es5-shim.js,es5-shim/4.0.5/es5-sham.js,html5shiv/3.7.2/src/html5shiv.js,react/0.13.3/react.js,react/0.13.3/JSXTransformer.js"></script>
</head>
<body>
<div id="container"></div>
<button id="clear">clear log</button>
<div id="log" style="border:1px solid red">
<script type="text/jsx">
function log(str){
document.getElementById('log').innerHTML+='<p>'+str+'</p>';;
}
document.getElementById('clear').onclick = function(){
document.getElementById('log').innerHTML='';
}
var Test = React.createClass({
getInitialState() {
log('getInitialState');
return {
value: this.props.value
};
},
componentWillReceiveProps(nextProps){
log('componentWillReceiveProps');
this.setState({
value: nextProps.value
});
},
shouldComponentUpdate(nextProps,nextState){
log('shouldComponentUpdate');
return true;
},
componentWillUpdate(nextProps,nextState){
log('componentWillUpdate');
},
componentWillMount(){
log('componentWillMount');
},
render() {
log('render');
return <span>{this.props.value}</span>
},
componentDidMount() {
log('componentDidMount');
},
componentDidUpdate(prevProps,prevState) {
log('componentDidUpdate');
},
componentWillUnmount(prevProps,prevState) {
log('componentWillUnmount');
}
});
var Hello = React.createClass({
getInitialState() {
return {
value:1,
destroyed:false
};
},
increase() {
this.setState({
value: this.state.value+1
});
},
destroy() {
this.setState({
destroyed: true
});
},
render: function() {
if(this.state.destroyed){
return null;
}
return <div>
<p>
<button onClick={this.increase}>increase</button>
<button onClick={this.destroy}>destroy</button>
</p>
<Test value={this.state.value}/>
</div>;
}
});
React.render(<Hello />, document.getElementById('container'));
</script>
</body>
</html>

TOP API

  1. React.createClass 创建组件类
  2. React.findDOMNode 从组件实例获取 dom 根节点
  3. React.render 渲染组件到 dom
  4. React.Children.* 操作 map/forEach children 工具类

实例

搭建评论应用

组件分解

顶层 CommentBox

  • 评论列表 CommentList
    • 单条评论 Comment
  • 评论表单 CommentForm
步骤
顶层
1
2
3
4
5
6
7
var CommentBox = React.createClass({
render(){
return (<div className='commentBox'>There is a CommentBox</div>)
}
})
React.render(<CommentBox />,document.getElementById('container'))
嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var CommentList = React.createClass({
render(){
return (<div className='commentList'>There is a CommentList</div>)
}
})
var CommentForm = React.createClass({
render(){
return (<div className='commentForm'>There is a CommentForm</div>)
}
})
var CommentBox = React.createClass({
render(){
return (<div className='commentBox'><h1>Comments</h1><CommentList /><CommentForm /></div>)
}
})
React.render(<CommentBox />,document.getElementById('container'))
属性传递
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
var Comment = React.createClass({
render(){
return (<div className='comment'><h2 className='commentAuthor'>{this.props.author}</h2>{this.props.children}</div>)
}
})
var CommentList = React.createClass({
render(){
return (<div className='commentList'><Comment author="作者 1">评论 1</Comment>
<Comment author="作者 2">评论 2</Comment></div>)
}
})
var CommentForm = React.createClass({
render(){
return (<div className='commentForm'>There is a CommentForm</div>)
}
})
var CommentBox = React.createClass({
render(){
return (<div className='commentBox'><h1>Comments</h1><CommentList /><CommentForm /></div>)
}
})
React.render(<CommentBox />,document.getElementById('container'))
使用 DOM 库 MARKED
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React系列教程:从入门到实战</title>
<script src="https://a.alipayobjects.com/??es5-shim/4.0.5/es5-shim.js,es5-shim/4.0.5/es5-sham.js,html5shiv/3.7.2/src/html5shiv.js,react/0.13.3/react.js,react/0.13.3/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/jsx">
</script>
</body>
</html>
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 Comment = React.createClass({
render(){
var rawMarkUp = marked(this.props.children.toString(),{sanitize:true})
return (<div className='comment'><h2 className='commentAuthor'>{this.props.author}</h2><span dangerouslySetInnerHTML={{__html:rawMarkUp}}></span></div>)
}
})
var CommentList = React.createClass({
render(){
return (<div className='commentList'><Comment author="作者 1">评论 1</Comment>
<Comment author="作者 2"> *评论 2* </Comment></div>)
}
})
var CommentForm = React.createClass({
render(){
return (<div className='commentForm'>There is a CommentForm</div>)
}
})
var CommentBox = React.createClass({
render(){
return (<div className='commentBox'><h1>Comments</h1><CommentList /><CommentForm /></div>)
}
})
React.render(<CommentBox />,document.getElementById('container'))
数据分离
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 localData = [
{author: "作者 1", text: "评论 1"},
{author: "作者 2", text: "*评论 2*"}
];
var Comment = React.createClass({
render(){
var rawMarkUp = marked(this.props.children.toString(),{sanitize:true})
return (<div className='comment'><h2 className='commentAuthor'>{this.props.author}</h2><span dangerouslySetInnerHTML={{__html:rawMarkUp}}></span></div>)
}
})
var CommentList = React.createClass({
render(){
var commentNodes = this.props.data.map(function(commentData){
return (<Comment author={commentData.author}>{commentData.text}</Comment>)
})
return (<div className='commentList'>{commentNodes}</div>)
}
})
var CommentForm = React.createClass({
render(){
return (<div className='commentForm'>There is a CommentForm</div>)
}
})
var CommentBox = React.createClass({
render(){
return (<div className='commentBox'><h1>Comments</h1><CommentList data={localData} /><CommentForm /></div>)
}
})
React.render(<CommentBox />,document.getElementById('container'))
从服务器取得数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React系列教程:从入门到实战</title>
<script src="https://a.alipayobjects.com/??es5-shim/4.0.5/es5-shim.js,es5-shim/4.0.5/es5-sham.js,html5shiv/3.7.2/src/html5shiv.js,react/0.13.3/react.js,react/0.13.3/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
<script src="http://fiddle.jshell.net/js/lib/mootools-1.2.5-core-nc.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/jsx">
</script>
</body>
</html>
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
var Comment = React.createClass({
render: function () {
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
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 CommentForm = React.createClass({
render: function () {
return (
<div className="commentForm">
Hello, world! I am a CommentForm.
</div>
);
}
});
var CommentBox = React.createClass({
loadCommentsFromServer: function () {
var self = this;
new Request.JSON({
url: this.props.url,
data: {
json: JSON.encode({
data: [
{
author: '作者 1',
text: '评论 1,' +Date.now()
},
{
author: '作者 2',
text: ' *评论 2,'+Date.now()+'* '
}
]
}),
delay:1
},
onSuccess(res) {
self.setState({data: res.data})
}
}).send();
},
getInitialState: function () {
return {data: []};
},
componentDidMount: function () {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
React.render(
<CommentBox url="/echo/json/" pollInterval={2000} />,
document.getElementById('container')
);
评论表单
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
var Comment = React.createClass({
render: function () {
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
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 CommentForm = React.createClass({
getInitialState() {
return {
name: '',
text: ''
}
},
updateField(field, e) {
console.log(e);
var state = {};
state[field] = e.target.value;
this.setState(state);
},
handleSubmit(e){
e.preventDefault();
this.props.onPost({
name:this.state.name,
text:this.state.text
});
this.setState({
name:'',
text:''
});
},
render: function () {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input placeholder="Your name" value={this.state.name} onChange={this.updateField.bind(this, 'name')}/>
<input placeholder="Say something..."
value={this.state.text} onChange={this.updateField.bind(this, 'text')}
/>
<input type="submit" value="Post" />
</form>
);
}
});
var database=[
{
author: '作者 1',
text: '评论 1,' + Date.now()
},
{
author: '作者 2',
text: ' *评论 2,' + Date.now() + '* '
}
];
var CommentBox = React.createClass({
loadCommentsFromServer: function () {
var self = this;
$.ajax({
url: this.props.url,
method:'post',
dataType:'json',
data: {
json:JSON.stringify({
data:database
})
},
success(res) {
console.log(res)
self.setState({data: res.data})
}
});
},
getInitialState: function () {
return {data: []};
},
handlePost(){
},
componentDidMount: function () {
this.loadCommentsFromServer();
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onPost={this.handlePost}/>
</div>
);
}
});
React.render(
<CommentBox url="/echo/json/" />,
document.getElementById('container')
);
通知重新渲染
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
var Comment = React.createClass({
render: function () {
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
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 CommentForm = React.createClass({
getInitialState() {
return {
name: '',
text: ''
}
},
updateField(field, e) {
var state = {};
state[field] = e.target.value;
this.setState(state);
},
handleSubmit(e){
e.preventDefault();
this.props.onPost({
author:this.state.name,
text:this.state.text
});
this.setState({
name:'',
text:''
});
},
render: function () {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input placeholder="Your name" value={this.state.name} onChange={this.updateField.bind(this, 'name')}/>
<input placeholder="Say something..."
value={this.state.text} onChange={this.updateField.bind(this, 'text')}
/>
<input type="submit" value="Post" />
</form>
);
}
});
var database=[
{
author: '作者 1',
text: '评论 1,' + Date.now()
},
{
author: '作者 2',
text: ' *评论 2,' + Date.now() + '* '
}
];
var CommentBox = React.createClass({
loadCommentsFromServer: function () {
var self = this;
$.ajax({
url: this.props.url,
method:'post',
dataType:'json',
data: {
json:JSON.stringify({
data:database
})
},
success(res) {
self.setState({data: res.data})
}
});
},
getInitialState: function () {
return {data: []};
},
handlePost(post){
database.push(post);
this.loadCommentsFromServer();
},
componentDidMount: function () {
this.loadCommentsFromServer();
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onPost={this.handlePost}/>
</div>
);
}
});
React.render(
<CommentBox url="/echo/json/" />,
document.getElementById('container')
);