组件从创建到被销毁的过程称为组件的生命周期,React的生命周期可分为三个阶段:挂载阶段、更新阶段、卸载阶段。不同的生命周期阶段对应了不同的生命周期函数。

挂载阶段

这个阶段组件被创建,执行初始化,并挂载到DOM中,流程依次是:(1)constructor(2)componentWillMount(3)render(4)componentDidMount

  1. constructor

这是ES6 class中的构造方法,组件被创建就会调用组件的构造方法,这个方法会传入一个props参数,此参数是父组件传入的参数对象,如果父组件没有传入对应的属性而组件自身定义了默认属性,那么这个props指向的就是组件的默认属性,必须要在这个方法中首先调用super(props)才能保证props传入组件使用。

在这里,类的数据类型就是函数,类本身就指向构造函数,假设有个类为A,所以通过typeof检测A的数据类型是function,而这条语句A === A.prototype.constructor结果也是true的。当有个class类为B,继承自A,此时在子类B中调用super()相当于A.prototype.constructor.call(this),super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例;这只是super做为函数调用的时候,当它做为对象使用的时候,相当于指向父类的原型A.prototype

  1. componentWillMount

组件挂载前调用,且只会被调用一次,此方法中使用this.setState()不会引起组件的重新渲染。

  1. render

这是定义组件必须要使用的方法,可以根据state和props返回一个React元素,注意,它只是一个纯函数,不能在内部执行有副作用的操作,也不负责DOM的渲染。

  1. componentDidMount

组件挂载完之后调用,且只会被调用一次,这时候DOM已经在页面挂载了,因此依赖DOM的操作可以在这里完成,还可以用于向服务端请求数据,在这调用this.setState()会引起组件的重新渲染。

更新阶段

组件挂载到DOM之后,组件的props和state会引起组件的更新,更新阶段的生命周期有:(1)componentWillReceiveProps(2)shouldComponentUpdate(3)componentWillUpdate(4)render(5)componentDidUpdate

  1. componentWillReceiveProps

这个方法只在props引起的组件更新过程中才会起作用,而单纯的state引起的组件更新是不会触发的,方法的参数nextProps是父组件传递给当前组件新的Props,也就是说,当父组件render方法被调用,引起组件的更新,但此时并不能保证传递给子组件的props发生改变,也有可能和之前的props相等,因此,在此方法中通常需要对比nextProps和this.props来决定是否执行后续操作。

  1. shouldComponentUpdate

这个方法决定是否继续执行更新过程,默认返回true,组件会继续更新,当返回false时,后续的(3)(4)(5)将不会执行,一般通过nextProps、nextState和当前的props、state对比来减少组件不必要的渲染,从而优化性能。

  1. componentWillUpdate

组件更新前执行的操作,一般很少用到,由于方法冗余,在后续版本逐渐移除,并且推出了函数组件操作状态的hook。注意(2)(3)方法中不能使用setState,否则会引起循环调用问题,render永远无法被调用,组件无法正常渲染。

  1. componentDidUpdate

组件更新后调用,可以作为操作更新后的DOM的地方,参数prevProps、prevState代表更新前的props和state。

卸载阶段

组件卸载过程只有一个生命周期函数:componentWillUnmount,在个方法在组件卸载前调用,通常执行一些清理操作,比如清除定时器,清除手动创建爱你的DOM元素等。

在React中,事件的命名采用驼峰命名法,事件响应函数必须以对象的形式赋值给事件属性,其实,React事件和DOM事件在使用上几乎没啥差别,但需要注意以下两点:

  1. DOM事件中可以通过回调函数返回false来阻止事件的默认行为,而在React中,必须要显式的调用事件对象的preventDefault方法来阻止事件的默认行为。
  2. 如果在某些场景下必须要用到DOM提供的原生事件,可以通过React事件对象的nativeEvent属性获取。

下面我们来看下在React中定义时间处理函数的几种方式:

1.使用箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mycomponent extends React.Component{
constructor(props){
super(props)
this.state = { number: 0 }
}

handleClick(e){
const { number } = this.state
this.setState({ number: ++number })
}

render(){
return(
<div>
<div>{this.state.number}</div>
<button onClick={(e)=>this.handleClick(e)}>点击</button>
</div>
)
}
}

由上面的案例可以看出,我们是可以直接在事件处理的大括号内直接定义函数语句,但这样最大的问题是,当代码逻辑比较复杂的时候,一方面是代码区块看起来比较混乱臃肿;另一方面,在每次render调用的时候,都会重新创建一个新的事件处理函数,给程序带来额外的开销,因为任何一个状态的变更都会引起组件的重新渲染。不过,大多数情况下是不必考虑这点性能问题的。

2.使用组件方法绑定this

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
class Mycomponent extends React.Component{
constructor(props){
super(props)
this.state = {
number: 0,
list: [1,4,5],
current: 1
}
this.handleClickOne = this.handleClickOne.bind(this)
}

handleClickOne(e){
const { number } = this.state
this.setState({ number: ++number })
}

handleClickTow(item ,e){
this.setState({ current: item })
}

render(){
const { number, current, list } = this.state

return(
<div>
<div>{number}</div>
{/*无参的情况*/}
<button onClick={this.handleClickOne}>点击1</button>
{/*有参的情况,事件属性值中绑定this*/}
<h2>{current}</h2>
{
list.map((item)=>(
<p onClick={this.handleClickTwo.bind(this,item)}>{item}</p>
))
}
</div>
)
}
}

这样的写法写起来麻烦,而且存在和方式一同样的问题。

3.属性初始化语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, {useState} from 'react';

const MyComponent = () => {
const [number, setNumber] = useState(0)

const handleClick = () => {
setNumber(++number)
}

return <div>
<div>{number}</div>
<button onClick={handleClick}>点击</button>
</div>
}

export default MyComponent

此方法结合ES7中的属性初始化语法和React Hook用法,不用手动绑定this,也没有重复渲染的问题,代码简洁可观,是现阶段(2020-6)广泛采用的一种方式。