React 生命周期(v16.0前 和 v16.4)

2,630 阅读8分钟

文章已同步至【个人博客】,欢迎访问【我的主页】😃
文章地址:blog.fanjunyang.zone/archives/re…

什么是生命周期函数?
就是不用自己手动的去调用,框架会在合适的时间自动的调用该函数

React v16.0前 生命周期

生命周期函数分类:

生命周期函数执行流程:

初始化阶段

constructor()

constructor是ES6对类的默认方法,通过 new 命令生成对象实例时自动调用该方法(调用一次)。
并且,该方法是类中必须有的,如果没有显示定义,则会默认添加空的constructor()方法。当存在constructor的时候必须手动调用super方法。
constructor中如果要访问this.props,则需要传入props

class Counter extends React.component{
    constructor(props){
        super(props); // 声明constructor时必须调用super方法
        console.log(this.props); // 可以正常访问this.props
    }
}

constructor 常用来初始化state

constructor(props){
	super(props)
	this.state = {number:0} // 初始化的状态
}

在类组件中也可以通过静态方法设置属性

class Counter extends React.component{
	static defaultProps = {name:"Fan"} // 默认属性
    constructor(props){
        super(props); // 声明constructor时必须调用super方法
        console.log(this.props); // 可以正常访问this.props
    }
}

挂载阶段

componentWillMount()

在组件挂载之前调用,且全局只调用一次。

如果在这个钩子里调用this.setState不会引起组件重新渲染,也可以把写在这里的内容写道到constructor()中,所以很少使用。

render()

render是一个React组件必须定义的生命周期函数,用来渲染DOM。

并必须 return 一个React元素(描述组件,即UI),不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM。
render必须是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有副作用)

不要在render里面修改state,会触发死循环导致栈溢出。

componentDidMount()

在组件挂载完成后调用,且全局只调用一次。
可以在这里使用refs,获取真实dom元素。

该钩子内也可以发起异步请求,并在异步请求中可以进行setState

更新阶段

在讲述此阶段前需要先明确下react组件更新机制。  

setState引起的state更新或父组件重新render引起的props更新,更新后的stateprops相对之前无论是否有变化,都将引起子组件的重新render

componentWillReceiveProps(nextProps )

props发生变化以及父组件重新渲染时都会触发该生命周期函数

在该钩子内可以通过参数nextProps获取变化后的props参数,
通过this.props访问之前的props。该生命周期内可以进行setState

shouldComponentUpdate(nextProps,nextState)

组件挂载之后,每次调用setState后都会调用shouldComponentUpdate判断是否需要重新渲染组件。就是通过比较nextPropsnextState及当前组件的this.propsthis.state的状态用来判断是否需要重新渲染

默认返回true,需要重新render,返回false则不触发渲染。

一般我们通过该函数来优化性能
例如:一个React项目需要更新一个小组件时,很可能需要父组件更新自己的状态。而一个父组件的重新更新会造成它其下所有的子组件重新执行render()方法,形成新的虚拟DOM,再用diff算法对新旧虚拟DOM进行结构和属性的比较,决定组件是否需要重新渲染
无疑这样的操作会造成很多的性能浪费,所以我们开发者可以根据项目的业务逻辑,在shouldComponentUpdate()中加入条件判断,从而优化性能

componentWillUpdate(nextProps,nextState)

组件即将被更新时触发

shouldComponentUpdate返回true或者调用forceUpdate之后,componentWillUpdate会被调用。

不能在该钩子中setState,否则会触发重复循环。

componentDidUpdate(prevProps, prevState)

此方法在组件更新后被调用(除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate)

可以操作组件更新的DOM,prevPropsprevState这两个参数指的是组件更新前的propsstate

该钩子内setState有可能会触发重复渲染,需要自行判断,否则会进入死循环。

卸载阶段

componentWillUnmount()

此方法在组件被卸载前调用

可以在这里执行一些清理工作,比如清除组件中使用的定时器、取消Redux的订阅事件、清除componentDidMount中手动创建的DOM元素等等,以避免引起内存泄漏。

测试代码

import React, { Component } from 'react';

class Father extends React.Component{
    static defaultProps = {name:"Fan"} // 默认属性
    constructor(props){
        super(props)
        this.state = {number:0} 
    }
    UNSAFE_componentWillMount(){
        console.log("Father: componentWillMount") // 1
    }
    shouldComponentUpdate(nextProps,nextState){
        console.log("Father: shouldComponentUpdate") // 
        // if(nextState.number%2 === 0){
        //     return true
        // }else{
        //     return false;
        // }
        return true;
    }
    componentWillUpdate(){
        console.log("Father: componentWillUpdate") // 
    }
    componentDidUpdate(){
        console.log("Father: componentDidUpdate") 
    }
    handleClick = ()=>{
        this.setState({number:this.state.number+1})
    }
    render(){
        console.log("Father: render")  // 2
        return(
            <div>
                <h1>父组件</h1>
                <p>{this.state.number}</p>
                <button onClick={this.handleClick}>+</button>
                <hr/>
                {this.state.number%2==0 ? <Son number={this.state.number}></Son> : null}
            </div>
        )
    }
    componentDidMount(){
        console.log("Father: componentDidMount") // 3
    }
}

class Son extends React.Component{
    UNSAFE_componentWillMount(){
        console.log("Son: componentWillMount") // 1
    }
    componentDidMount(){
        console.log("Son: componentDidMount") // 3
    }
    componentWillReceiveProps(){
        console.log("Son: componentWillReceiveProps")
    }
    shouldComponentUpdate(){
        console.log("Son: shouldComponentUpdate")
        // return false;
        return true;
    }
    componentWillUpdate(){
        console.log("Son: componentWillUpdate") // 
    }
    componentDidUpdate(){
        console.log("Son: componentDidUpdate") 
    }
    render(){
        console.log("Son: render")
        return(
            <div>
                <h1>子组件</h1>
                <p>{this.props.number}</p>
            </div>
        )
    }
    componentWillUnmount(){
        console.log("Son: componentWillUnmount") 
    }
}
export default Father;

运行结果(需要下去自己慢慢分析):

React v16.4 生命周期

移除的生命周期函数:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

虽然废弃了这三个生命周期方法,但是为了向下兼容,将会做渐进式调整。
在16.3 版本并未删除这三个生命周期,同时还为它们新增以 UNSAFE_ 前缀为别名的三个函数 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()
在 16.4 版本给出警告将会弃用 componentWillMount()componentWillReceiveProps()componentWillUpdate() 三个函数
然后在 17.0 版本将会删除 componentWillMount()componentWillReceiveProps()componentWillUpdate() 这三个函数,会保留使用 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()

新增的声明周期函数:

  • static getDerivedStateFromProps(nextProps, prevState)
  • getSnapshotBeforeUpdate(prevProps, prevState)

为什么改变生命周期?

从上面的生命周期的图中可以看出,被废弃的三个函数都是在render之前,因为fiber的出现,很可能因为高优先级任务的出现而打断现有任务导致它们会被执行多次,这肯定不是你想要的结果。

另外的一个原因则是,React想约束使用者,好的框架能够让人不得已写出容易维护和扩展的代码。

有篇文章感觉写的很好新老生命周期对比

static getDerivedStateFromProps(nextProps, prevState)

在每次渲染之前都会调用,不管造成重新渲染的原因是什么,不管初始挂载还是后面的更新都会调用,这一点和UNSAFE_componentWillReceiveProps不同(只有当父组件造成重新渲染时才调用)

该方法传入的两个参数:nextProps表示父组件传入的值,prevState表示组件自身的state

使用该方法,需要在该方法中返回一个对象或null:如果返回的是对象,则会更新 state,如果返回的是null,则表示不更新。

使用该方法的时候需要初始化state,否则在控制台中会出现警告信息,不能在该方法内部,调用this.state

getSnapshotBeforeUpdate(prevProps, prevState)

这个方法在 render() 之后,componentDidUpdate() 之前调用。

该方法传入的两个参数:prevProps表示更新前的 props,prevState表示更新前的 state

返回值称为一个快照(snapshot),如果不需要 snapshot,则必须显示的返回 null —— 因为返回值将作为 componentDidUpdate() 的第三个参数使用。所以这个函数必须要配合 componentDidUpdate() 一起使用

这个函数的作用是在真实 DOM 更新(componentDidUpdate)前,获取一些需要的信息(类似快照功能),然后作为参数传给 componentDidUpdate。例如:在getSnapShotBeforeUpdate中获取滚动位置,然后作为参数传给 componentDidUpdate,就可以直接在渲染真实的 DOM 时就滚动到需要的位置。

componentDidCatch(err,info)

在上面没说,这里说一下

任何子组件在渲染期间,生命周期方法中或者构造函数 constructor 发生错误时调用。

该方法传入的两个参数:err表示抛出的错误,info表示带有componentStack key的对象,其中包含有关组件引发错误的栈信息。

错误边界不会捕获下面的错误:

  • 事件处理 (Event handlers) (因为事件处理不发生在 React 渲染时,报错不影响渲染)
  • 异步代码 (Asynchronous code) (例如:setTimeout or requestAnimationFrame callbacks)
  • 服务端渲染 (Server side rendering)
  • 错误边界本身(而不是子组件)抛出的错误

测试代码

import React, { Component } from 'react';

class Father extends Component{
    constructor(props){
        super(props)
        this.state = {number:0} 
    }
    handleClick = ()=>{
        this.setState({number:this.state.number+1})
    }
    render(){
        return(
            <div>
                <h1>父组件</h1>
                <p>{this.state.number}</p>
                <button onClick={this.handleClick}>+</button>
                <hr/>
                <Son number={this.state.number}></Son>
            </div>
        )
    }
}
class Son extends React.Component{
    state = {
        number:0
    }
    // nextProps,prevState
    // nextProps 就是父给子传递的过来的新的数据 
    // prevState 是子的上一次的状态
    //每次更新都会触发
    static getDerivedStateFromProps(nextProps,prevState){
        // nextProps 表示父向下传递的新的值 1 2 3 4 5
        // prevState  0 0 0 
        console.log(nextProps,prevState)  // {number: 0}  {number: 0}
        let {number} = nextProps; 
        // console.log(number)
        // if(number%2 === 0){
        //     return { number:number+10 }
        // }else{
        //     return { number:number+100 }
        // }

        // prevState 表示是子组件的上一次状态
        if(number%2 === 0){
            console.log(prevState.number)
            return { number:prevState.number + number+10 }
        }else{
            console.log(prevState.number)
            return { number:prevState.number + number+100 }
        }
    }
    handleChange = ()=>{
        this.setState({
            number:this.state.number+1000
        })
    }
    render(){
        return(
            <div>
                <h1>子组件</h1>
                <p>{this.state.number}</p>
                <button onClick={this.handleChange}>改变状态</button>
            </div>
        )
    }
}

export default Father;

运行效果:

import React, { Component } from 'react';

class News extends Component {
    constructor(props) {
        super(props)
        this.scrollRef = React.createRef();
    }
    state = {
        // news:["news6","news5","new4","news3","news2","new1"]
        news: []
    }
    componentDidMount() {
        this.timer = setInterval(() => {
            this.setState({
                news: [`${this.state.news.length}`, ...this.state.news]
            })
        }, 1000)
    }
    componentWillUnmount() {
        clearInterval(this.timer)
    }
    // 获取更新之前dom的快照
    getSnapshotBeforeUpdate() {
        // console.log(this.scrollRef.current.scrollHeight)
        // 在getSnapshotBeforeUpdate中返回了一个值,这个值会给componedDidUpdate的最后一个参数
        return this.scrollRef.current.scrollHeight;
    }
    componentDidUpdate(prevProps, prevState, lastScrollHeight) {
        console.log(lastScrollHeight)
        console.log(this.scrollRef.current.scrollTop)  // 0
        let scrollTop = this.scrollRef.current.scrollTop;
        this.scrollRef.current.scrollTop = scrollTop + (this.scrollRef.current.scrollHeight - lastScrollHeight)
    }
    render() {
        let styles = {
            height: "100px",
            width: "200px",
            border: "1px solid red",
            overflow: "auto"
        }
        return (
            <ul style={styles} ref={this.scrollRef}>
                {
                    this.state.news.map((item, index) => <li key={index}>{item}</li>)
                }
            </ul>
        )
    }
}

export default News;

运行效果:


QAQ