React 生命周期

生命周期函数也叫作:生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子

通过一个案例来学习

在这里插入图片描述

首先,在 render 中不要写 setState(),不然会死循环的,因为你执行 setState 就会触发 render()

下面介绍两个生命周期函数:

  • 组件挂载完毕:componentDidMount

  • 组件将要卸载:componentWillUnmount

在如下的例子中有使用

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
<script type="text/babel">
//创建组件
//生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Life extends React.Component{

state = {opacity:1}

death = ()=>{
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}

//组件挂完毕
componentDidMount(){
console.log('componentDidMount');
this.timer = setInterval(() => {
//获取原状态
let {opacity} = this.state
//减小0.1
opacity -= 0.1
if(opacity <= 0) opacity = 1
//设置新的透明度
this.setState({opacity})
}, 200);
}

//组件将要卸载
componentWillUnmount(){
//清除定时器
clearInterval(this.timer)
}

//初始化渲染、状态更新之后
render(){
console.log('render');
return(
<div>
<h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Life/>,document.getElementById('test'))
</script>

生命周期流程图(旧)

请添加图片描述

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<script type="text/babel">
/*
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
3. render() =====> 必须使用的一个
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//创建组件
class Count extends React.Component{

//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}

//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}

//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}

//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}

//组件将要挂载的钩子
componentWillMount(){
console.log('Count---componentWillMount');
}

//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}

//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}

//控制组件更新的“阀门”,返回 true 或者 false
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}

//组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}

//组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}

render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}

//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}

changeCar = ()=>{
this.setState({carName:'奥拓'})
}

render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}

//子组件B
class B extends React.Component{
//组件将要接收 新的props 的钩子,第一次是不算的,可以以叫做 newProps
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}

//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}

//组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}

render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}

//渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'))
</script>

UNSAFE 的理解

这是官网上对于 UNSAFE 的解释,异步渲染之更新
在这里插入图片描述


生命周期流程图(新)

在这里插入图片描述


新旧对比

新的生命周期:弃用了原来的三个带 will 的生命周期
在这里插入图片描述

增加了两个生命周期

在这里插入图片描述

static getDerivedStateFromProps

1
2
3
4
5
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}

getSnapshotBeforeUpdate 获取快照

1
2
3
4
5
//在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return '快照'
}

使用场景如下:

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
<script type="text/babel">
class NewsList extends React.Component{

state = {newsArr:[]}

componentDidMount(){
setInterval(() => {
//获取原状态
const {newsArr} = this.state
//模拟一条新闻
const news = '新闻'+ (newsArr.length+1)
//更新状态
this.setState({newsArr:[news,...newsArr]})
}, 1000);
}

getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight
}

componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}

render(){
return(
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList/>,document.getElementById('test'))
</script>

Diff 算法

  • 匹配的最小力度是标签
  • 标签中的标签也会进行 diff 算法的比较

在这里插入图片描述

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">
class Time extends React.Component {
state = { date: new Date() };

componentDidMount() {
setInterval(() => {
this.setState({
date: new Date(),
});
}, 1000);
}

render() {
// 匹配的最小力度是标签
return (
<div>
<h1>hello</h1>
<input type="text" />
<span>
现在是:{this.state.date.toTimeString()}
<input type="text" />
</span>
</div>
);
}
}

ReactDOM.render(<Time />, document.getElementById("test"));
</script>

key 的作用

  • 用来配合 diff 算法的
  • diff 先检测 key 值,如果相等的话,就检测内容
  • 如果不相等的话 ,直接生成新的 DOM
  • 如果在节点里有输入型的 DOM 节点,通过 index 作为 key 的话,可能会发生如下的错误

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
index 作为 key 值
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>

更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
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
<script type="text/babel">
/*
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?

1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面

2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。

3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
*/

/*
慢动作回放----使用index索引值作为key

初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>

更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

-----------------------------------------------------------------

慢动作回放----使用id唯一标识作为key

初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>


*/
class Person extends React.Component{

state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}

add = ()=>{
const {persons} = this.state
const p = {id:persons.length+1,name:'小王',age:20}
this.setState({persons:[p,...persons]})
}

render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj)=>{
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}

ReactDOM.render(<Person/>,document.getElementById('test'))
</script>