React生命周期与常见使用场景
前言
对于react的生命周期一直是在工作中很重要的,我们往往需要在需要的时间段做自己需要的事情,在 React 中,生命周期大致上可以分为初始化(Initialization)、挂载(Mounting)、更新(Updating)和卸载(Unmounting) 这几个阶段,每个阶段又会分别调用不同的生命周期函数。当然随着react版本的更新,也会出现相对的变化,所以我们现在来参考总结下
大部分团队不见得会跟进升到16版本,所以16前的生命周期还是很有必要掌握的,何况16也是基于之前的修改
一 初始化阶段 Initialization
也就是以下代码中类的构造方法( constructor() ),Test类继承了react Component这个基类,也就继承这个react的基类,才能有render(),生命周期等方法可以使用,这也说明为什么 函数组件不能使用这些方法的原因。super(props) 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,功子组件读取(组件中props只读不可变,state可变)。 而 constructor() 用来做一些组件的初始化工作,如定义this.state的初始内容。
注意:
constructor
不算生命周期函数。constructor
我们叫构造函数,它是ES6的基本语法。虽然它和生命周期函数的性质一样,但不能认为是生命周期函数。但是你要心里把它当成一个生命周期函数,我个人把它看成React的Initialization
阶段,定义属性(props)和状态(state)。
import React, {
Component } from 'react'
class Test extends Component {
constructor(props) {
super(props)
}
}
二 挂载阶段 Mounting
componentWillMount
: 在组件即将被挂载到页面的时刻执行。
在组件挂载到DOM前调用,且只会被调用一次,在这边调用this.setState不会引起组件重新渲染,也可以把写在这边的内容提前到constructor()中,所以项目中很少用。
render
: 页面state或props发生变化时执行。
根据组件的props和state(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render) ,return一个React元素(描述组件,即UI),不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM。render是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有副作用),不能在里面执行this.setState,会有改变组件状态的副作用。
componentDidMount
: 组件挂载完成时被执行。
组件挂载到DOM后调用,且只会被调用一次
export default class App extends Component {
componentWillMount () {
console.log('componentWillMount----组件将要挂载到页面的时刻')
}
render() {
console.log('render---组件挂载中.......')
return (
<div>
<TodoList/>
<ReduxTodoList/>
</div>
)
}
componentDidMount () {
console.log('componentDidMount----组件挂载完成的时刻执行')
}
}
打印的结果:
componentWillMount----组件将要挂载到页面的时刻执行
render----开始挂载渲染
componentDidMount----组件挂载完成的时刻执行
这就是挂载阶段的生命周期的执行顺序,当然他跟书写顺序没有关系
注意:
componentWillMount
和componentDidMount
这两个生命周期函数,只在页面刷新时执行一次,而render函数是只要有state和props变化就会执行,这个初学者一定要注意。
三 更新阶段 Updation
这个阶段是较为复杂的阶段,由上面的图可以看出造成组件更新有两类的情况,需要先明确下React组件更新机制。setState引起的state更新或父组件重新render引起的props更新,更新后的state和props相对之前无论是否有变化,都将引起子组件的重新render
-
shouldComponentUpdate
在组件更新之前,自动被执行,它要求返回一个布尔类型的结果,必须有返回值,这里就直接返回一个true了(真实开发中,这个是有大作用的,能有有效的解决性能的问题),如果你返回了false,这组件就不会进行更新了。 简单点说,就是返回true,就同意组件更新;返回false,就反对组件更新。此方法通过比较nextProps,nextState及当前组件的this.props,this.state,返回true时当前组件将继续执行更新过程,返回false则当前组件更新停止,以此可用来减少组件的不必要渲染,优化组件性能。 -
componentWillUpdate
在组件更新之前,但shouldComponenUpdate
之后被执行,但是shouldComponenUpdate
返回的是false的时候就会反对组件的更新,这个函数就不会执行 -
componentDidUpdate
在组件更新之后执行,它是组件更新的最后一个环节
export default class App extends Component {
shouldComponentUpdate () {
console.log ('1.shouldComponentUpdate----组件更新之前')
return true
}
componentWillUpdate () {
console.log ('2.componentWillUpdate---组件更新前,shouldComponentUpdate函数之后执行 ')
}
render() {
console.log('3.render---组件挂载渲染.......')
return (
<div>
<TodoList/>
<ReduxTodoList/>
</div>
)
}
componentDidUpdate () {
console.log('componentDidUpdate----组件更新完成的时刻执行')
}
}
打印结果与执行顺序
1-shouldComponentUpdate---组件发生改变前执行
2-componentWillUpdate---组件更新前,shouldComponentUpdate函数之后执行
3-render----开始挂载渲染
4-componentDidUpdate----组件更新之后执行
- 那么上图中的
componentWillReceiveProps
在什么时候执行呢,其实子组件接收到父组件传递过来的参数,父组件render函数重新被执行,这个生命周期就会被执行。
注意:
使用componentWillReceiveProps
组件第一次存在于Dom中,函数是不会被执行的;
如果已经存在于Dom中,函数才会被执行。
当父组件传递的 props 即将引起组件更新时会被调用,该方法接受一个参数指的是当前父组件传递给组件的最新的 props 状态数据。在这个生命周期方法中,我们可以根据比较 nextProps 和 this.props 新旧 props 的值查明 props 是否改变,依次做一些数据处理的逻辑。
销毁阶段 Unmounting
这个阶段就只一个生命周期函数:componentWillUnmount
,此方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清楚componentDidMount
中手动创建的DOM元素、解绑事件等,以避免引起内存泄漏
总结利用生命周期函数的优化页面:
- 在
componentDidMount
生命周期函数里请求ajax,建议在componentDidMount函数里执行,因为在render里执行,会出现很多问题,比如一直循环渲染;在componentWillMount里执行,在使用RN时,又会有冲突。所以强烈建议在componentDidMount函数里作ajax请求。 - 在父组件更新的时候传递到子组件的时候导致pros反正改变,子组件的render函数不停触发,出现性能问题,我们可以这个一般发生在更新阶段,所以利用shouldComponentUpdate
shouldComponentUpdate(nextProps,nextState){
if(nextProps.content !== this.props.content){
return true
}else{
return false
}
}
React v16.4 的生命周期
变更缘由
原来(React v16.0前)的生命周期在React v16推出的Fiber之后就不合适了,因为如果要开启async rendering,在render函数之前的所有函数,都有可能被执行多次。
原来(React v16.0前)的生命周期有哪些是在render前执行的呢?
- componentWillMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
如果开发者开了async rendering,而且又在以上这些render前执行的生命周期方法做A JAX请求的话,那AJAX将被无谓地多次调用。。。明显不是我们期望的结果。而且在componentWillMount里发起AJAX,不管多快得到结果也赶不上首次render,而且componentWillMount在服务器端渲染也会被调用到(当然,也许这是预期的结果),这样的IO操作放在componentDidMount里更合适。
禁止不能用比劝导开发者不要这样用的效果更好,所以除了shouldComponentUpdate,其他在render函数之前的所有函数(componentWillMount
,componentWillReceiveProps
,componentWillUpdate
)都被getDerivedStateFromProps
替代。
也就是用一个静态函数getDerivedStateFromProps
来取代被deprecate的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state
新引入了两个新的生命周期函数:
1. getDerivedStateFromProps
getDerivedStateFromProps 本来(React v16.3中)是只在创建和更新(由父组件引发部分),也就是不是不由父组件引发,那么getDerivedStateFromProps也不会被调用,如自身setState引发或者forceUpdate引发
这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps无论是Mounting
还是Updating
,也无论是因为什么引起的Updating,全部都会被调用,具体可看React v16.4 的生命周期图。
getDerivedStateFromProps(props, state) 在组件创建时和更新时的render方法之前调用,它应该返回
一个对象来更新状态,或者返回null来不更新任何内容
每当父组件引发当前组件的渲染时,getDerivedStateFromProps 会被调用,这样我们有机会可以根据新的 props 和之前的 state 来调整新的 state。如果放在三个被 deprecate 生命周期函数中实现比较纯,没有副作用的话,就可以搬到 getDerivedStateFromProps 了;如果不幸做了类似 AJAX 之类的操作,首先要反省为什么自己当初这么做,然后搬到 componentDidMount 或者 componentDidUpdate 中去。目前当你使用三个被 deprecate 生命周期函数时,开发模式下会有红色警告,要求你使用 UNSAFE_ 前缀。可能会在打一次大版本更新时直接废弃,所以那些抱有侥幸心理的开发者还是放弃使用吧。
2. getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
官网给的例子:
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//我们是否要添加新的 items 到列表?
// 捕捉滚动位置,以便我们可以稍后调整滚动.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
//如果我们有snapshot值, 我们已经添加了 新的items.
// 调整滚动以至于这些新的items 不会将旧items推出视图。
// (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={
this.listRef}>{
/* ...contents... */}</div>
);
}
}
注意:
当你同时使用了 getDerivedStateFromProps、 getSnapshotBeforeUpdate 新的生命周期 API 和 deprecate 生命周期函数时,deprecate 生命周期函数会被直接忽略掉,并不会适时执行!