React的生命周期
生命周期就函数就是指在某一个时间刻会自动调用执行的函数,React生命周期分为三个阶段
挂载:组件被创建并插入真实dom
渲染(更新):组建的props或state发生变化时触发的组件更新
卸载:组件被从DOM中移除
生命周期有两个版本,react16.3版本引入了新版本生命周期的方法【new life cycle】,react16.3之前,react使用的都是旧版本的生命周期【old life cycle】
old life cycle
new life cycle
挂载(Mounting):
constructor
constructor 只会在组件初始化的时候触发一次
组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,我们必须在构造函数中执行
super(props),否则无法在构造函数中拿到this。如果不初始化 state 或不进行方法绑定,则不需要为 React
组件实现构造函数Constructor。 constructor中通常只做两件事:【初始化组件state,给事件处理方法绑定this】
constructor(props){
// 要在构造函数中调用 setState,可以直接给 state 设置初始值
super(props);
this.state={
count: 0,
inputValue:"",
},
this.handleClick = this.handleClick.bind(this)
}
getDerivedStateFromProps:
该函数会在组件化实例化后和重新渲染前调用(生成 VirtualDOM 之后,实际 DOM 挂载之前)
这是个静态方法,所以不能在这个函数里使用,this,有两个参数 props 和 state,分别指接收到的新参数和当前组件的 state对象,这个函数会返回一个对象用来更新当前的 ,state 对象,如果不需要更新可以返回 null。
无论是父组件的更新、props的变化或通过 setState 更新组件内部的 State,它都会被调用。
该生命周期函数必须有返回值,返回一个对象来更新 State,若返回 null 表明新 props 不更新 state。新的生命周期加了新特性,当组件实例化时,该方法替代了componentWillMount,而当接收新的 props 时,该方法替代了 componentWillReceiveProps 和componentWillUpdate。
适用于表单获取默认值。
static getDerivedStateFromProps(props, state)
import React, { Component } from 'react'; //(0)【引入组件第一种写法】
import '../App.css';
class Homes extends Component { //(0-1)对应第一种引入react引入方法类写法
//(1)接下来两行为固定写法
constructor(props) {
// 要在构造函数中调用 setState,可以直接给 state 设置初始值
super(props);
this.state = {
count: 0,
inputValue: "",
},
this.handleClick = this.handleClick.bind(this)
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
static getDerivedStateFromProps(props, state) {
if (props.count !== state.count) {
return {
count: props.count
}
}
return null
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.count}</h1>
</div>
)
}
};
export default Homes;
现在可以显式传入 counter ,但是这里有个问题,如果想要通过点击实现 state.counter 的增加,但这时会发现值不会发生任何变化,一直保持 props 传进来的值。这是由于在 React 16.4^ 的版本中 setState 和 forceUpdate 也会触发这个生命周期,所以当组件内部 state 变化后,就会重新走这个方法,同时会把 state 值赋值为 props 的值。因此需要多加一个字段来记录之前的 props 值,这样就会解决上述问题。
import React, { Component } from 'react'; //(0)【引入组件第一种写法】
import '../App.css';
class Homes extends Component { //(0-1)对应第一种引入react引入方法类写法
//(1)接下来两行为固定写法
constructor(props) {
// 要在构造函数中调用 setState,可以直接给 state 设置初始值
super(props);
this.state = {
count: 0,
preCount:0,
inputValue: "",
},
this.handleClick = this.handleClick.bind(this)
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
static getDerivedStateFromProps(props, preCount) {
if (props.count !== state.preCount) {
return {
count: props.count,
preCount:props.counter
}
}
return null
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.count}</h1>
</div>
)
}
};
export default Homes;
render:
组件初始化,页面state或props发生变化时执行。 render是React 中最核心的方法,一个组件中必须要有这个方法,它会根据状态
state 和属性 props 渲染组件。这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:
a.React元素这里包括原生的DOM 以及React 组件。
b.数组和Fragment(片段):可以返回多个元素。
c.Portals(插槽):可以将子元素渲染到不同的DOM中。
d.字符串和数字:被渲染成DOM中的text节点。
e.布尔值或者null:不渲染任何内容。
componentDidMount :
在组件挂载完成之后 (插入DOM树后) 立即调用。
componentDidMount()会在组件挂载后(插入 DOM 树中)立即调用。
该阶段通常执行依赖于DOM的操作,发送网络请求,添加订阅消息(会在componentWillUnmount取消订阅),添加事件监听,如果使用了Redux之类的数据管理工具也能触发action处理数据变化逻辑。此钩子函数中允许使用setState改变组件内的State。
注意:该生命周期函数在进行服务器端渲染时候不会触发(仅客户端有效)。如果在componentDidMount中调用setState,就会触发一次额外的渲染,多调用了一次render函数,由于他是在浏览器刷新屏幕前执行的,所以用户对此没有感知的,但是应当避免这样使用,这样带来的一定的性能问题,精良在constructor中初始化state对象。
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
componentDidMount () {
this.setState({
count: 1
})
}
render () {
return (
<div className="count">
counter值: { this.state.count }
</div>
)
}
}
更新(Updation):
setState、useState、props变化、forceUpdate会触发组件的更新,但是组件是否从新渲染取决于shouldComponentUpdate。
shouldComponentUpdate:
函数会在组件更新之前,自动被执行。它要求返回一个布尔类型的结果,必须有返回值。返回true时组件更新,返回false则不更新。
当props或者state发生变化,在重新渲染执行之前,第一个是即将更新的props值,第二个是即将更新后的state值,可以根据更新前后的props或者state来比较加一些限制条件,决定是否更新进行性能优化,当该方法返回的布尔值false告知react无需重新渲染时,render,UNSAFE_componentWillUpdate和conponentDidUpdate等生命周期钩子函数都不会被触发。
a. 此钩子函数在初始化渲染和使用forceUpdate方法的情况下不会被触发, 使用forceUpdate会强制更新
b. 禁止在此函数中使用setState方法,会导致循环调用
shouldComponentUpdate(){
console.log('shouldComponentUpdate---组件发生改变前执行')
return true
}
现在就可以在控制台,console里看到结果了,并且结果是每次文本框发生改变时都会随着改变。如果你返回了
false,这组件就不会进行更新了。 简单点说,就是返回true,就同意组件更新;返回false,就反对组件更新。
getSnapshotBeforeUpdate (新):
保存状态快照。该生命周期函数会在组件即将挂载时触发,它的触发在 render 渲染函数之后。 这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,标识更新之前的props和state,这个函数必须要和componentDidUpdate一起使用,并且要有一个返回值,默认是null,这个返回值座位第三个参数传递给componentDidUpdate。该生命周期函数执行完毕后就会立即触发componentDidUpdate 生命周期钩子。
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('#enter getSnapshotBeforeUpdate');
return 'foo';
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('#enter componentDidUpdate snapshot = ', snapshot);
}
该生命周期函数能让你捕获某些从 DOM 中才能获取的(可能会变更的例如滚动位置),此生命周期返回的任何值都会作为第三个参数传递给 componentDidUpdate()。如不需要传递任何值,那么请返回 null。
componentDidUpdate:
在组件更新之后执行,它是组件更新的最后一个环。 该方法有三个参数【componentDidUpdate(prevProps,prevState, snapshot){}】
a. prevProps: 更新前的props
b. prevState: 更新前的state
c. snapshot: getSnapshotBeforeUpdate()生命周期的返回值
组件每次重新渲染后会触发 componentDidUpdate 但是只有在组件首次渲染(即初始化)时触发的是componentDidMount钩子函数,初始化渲染后componentDidMount 不会再被触发。 操作 DOM;发送网络请求。 在componentDidUpdate 生命周期函数中调用 setState 方法时,确实需要加上条件判断,以避免死循环的发生。通过判断 prevProps、prevState 和 this.state 之间的数据变化,来判断是否执行相关的 state 变更逻辑,这使得尽管在 componentDidUpdate 中调用了 setState 进行再更新,如果条件不成立state就不会进行更新,从而避免了死循环的发生。
componentDidUpdate(prevProps){
if (this.props.id !== prevProps.id){
this.fetchData(this.props.id);
}
}
卸载(Unmounting):
componentWillUnmount:
它是在组件去除时执行。在组件卸载和销毁之前触发。可以利用这个生命周期方法进行事件的解绑。 用于移除事件监听器;取消网络请求;取消定时器;解绑DOM 事件。 在该方法中调用 setState 不会触发 render,因为所有的更新队列,更新状态都被重置为null。这个生命周期在一个组件被卸载和销毁之前被调用,因此不应该再这个方法中使用setState,因为组件一旦被卸载,就不会再装载,也就不会重新渲染。
新版本的生命周期就开始废弃了componentWillMount、componentWillReceiveProps、componentWillUpdate的三个钩子,并为这几个钩子提供了别名
- UNSAFE_componentWillMount:当组件被渲染出来之前
- UNSAFE_componentWillReceiveProps:当组件收到新的props之前
- UNSAFE_componentWillUpdate:组件更新前
父子组件加载顺序
父子组件初次加载会在子组件挂挂载完成后继续父组件的挂载
- Parent 组件: constructor()
- Parent 组件: getDerivedStateFromProps()
- Parent 组件: render()
- Child 组件: constructor()
- Child 组件: getDerivedStateFromProps()
- Child 组件: render()
- Child 组件: componentDidMount()
- Parent 组件: componentDidMount()
当执行render子组件的时候,才会进入子组件的生命周期,子组件的周期结束后,再回到上级的周期。 改变子组件自身状态
Child 组件: getDerivedStateFromProps()
- Child 组件: shouldComponentUpdate()
- Child 组件: render()
- Child 组件: getSnapshotBeforeUpdate()
- Child 组件: componentDidUpdate()
修改父组件传入给子组件的 props 当父组件修改传递的 props 值时,会触发父子组件的更新
Parent 组件: getDerivedStateFromProps()
- Parent 组件: shouldComponentUpdate()
- Parent 组件: render()
- Child 组件: getDerivedStateFromProps()
- Child 组件: shouldComponentUpdate()
- Child 组件: render()
- Child 组件: getSnapshotBeforeUpdate()
- Parent 组件: getSnapshotBeforeUpdate()
- Child 组件: componentDidUpdate()
- Parent 组件: componentDidUpdate()
只修改父组件的值 当父组件的值被修改时,会触发父组件的 render() 方法重新渲染,并递归地 r-render 子组件。这个过程中,会依次触发所有子组件的生命周期方法:
- Parent 组件: getDerivedStateFromProps()
- Parent 组件: shouldComponentUpdate()
- Parent 组件: render()
- Child 组件: getDerivedStateFromProps()
- Child 组件: shouldComponentUpdate()
- Child 组件: render()
- Child 组件: getSnapshotBeforeUpdate()
- Parent 组件: getSnapshotBeforeUpdate()
- Child 组件: componentDidUpdate()
- Parent 组件: componentDidUpdate()
所以不管父组件有没有把数据传递给子组件,只要父组件
setState,都会走一遍子组件的更新周期。而且子组件被动更新会比主动更新所执行的流程多出来一个
componentWillReceiveProps 方法。
子组件修改自身state
当我们修改子组件中的数据时,只有子组件会经历更新生命周期,而父组件不会。
- 子组件 getDerivedStateFromProps
- 子组件 shouldComponentUpdate
- 子组件 render
- 子组件 getSnapShotBeforeUpdate
- 子组件 componentDidUpdate
卸载子组件,触发父组件的重新渲染、子组件销毁
- Parent 组件: getDerivedStateFromProps()
- Parent 组件: shouldComponentUpdate()
- Parent 组件: render()
- Parent 组件: getSnapshotBeforeUpdate()
- Child 组件: componentWillUnmount()
- Parent 组件: componentDidUpdate()
当子组件自身状态改变时,不会对父组件产生副作用的情况下,父组件不会进行更新,即不会触发父组件的生命周期,当父组件中状态发生变化(包括子组件的挂载以及卸载)时,会触发自身对应的生命周期以及子组件的更新。
完结~