React专题:生命周期

本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出

来我的
GitHub repo 浏览完全的专题文章

来我的
个人博客 取得无与伦比的浏览体验

生命周期,望文生义,就是从生到死的历程。

而生命周期钩子,就是从生到死历程当中的症结节点。

普通人的终身有哪些生命周期钩子呢?

  • 诞生
  • 考上大学
  • 第一份事情
  • 买房
  • 完婚
  • 生子
  • 孩子的生命周期钩子
  • 退休
  • 临终遗嘱

每到症结节点,我们总愿望有一些寻思时刻,因为这时刻做出的决议计划会转变人生的走向。

React组件也一样,它会给开发者一些寻思时刻,在这里,开发者可以转变组件的走向。

异步衬着下的生命周期

React花了两年时刻祭出Fiber衬着机制。

简朴来讲,React将diff的历程叫做Reconciliation。之前这一历程是一挥而就的,Fiber机制把它改成了异步。异步妙技将在接下来的版本中逐步解锁。

明显是一段同步代码,怎样就异步了呢?

道理是Fiber把任务切成很小的片,每实行一片就把控制权交还给主线程,待主线程忙完手头的活再来实行剩下的任务。固然假如某一片的实行时刻就很长(比方死循环),那就没主线程什么事了,该崩溃崩溃。

这会给生命周期带来什么影响呢?

影响就是挂载和更新之前的生命周期都变的不可靠了。

为何这么讲?因为Reconciliation这个历程有可以停息然后继续实行,所以挂载和更新之前的生命周期钩子就有可以不实行或许屡次实行,它的表现是不可预期的。

因而16今后的React生命周期迎来了一波大换血,以下生命周期钩子将被逐步烧毁:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

看出特点了么,都是带有will的钩子。

如今React为这几个生命周期钩子供应了别号,分别是:

  • UNSAFE_componentWillMount
  • UNSAFE_componentWillReceiveProps
  • UNSAFE_componentWillUpdate

React17将只供应别号,完全烧毁这三个大活宝。取这么个别号意义就是让你用着恶心。

constructor()

React借用class类的constructor充任初始化钩子。

React险些没做什么四肢,然则因为我们只允许经由过程特定的门路给组件通报参数,所以constructor的参数实际上是被React划定好的。

React划定constructor有三个参数,分别是propscontextupdater

  • props是属性,它是不可变的。
  • context是全局上下文。
  • updater是包括一些更新要领的对象,this.setState终究挪用的是this.updater.enqueueSetState要领,this.forceUpdate终究挪用的是this.updater.enqueueForceUpdate要领,所以这些API更多是React内部运用,暴露出来是以备开发者不时之需。

在React中,因为一切class组件都要继续自Component类或许PureComponent类,因而和原生class写法一样,要在constructor里起首挪用super要领,才取得this

constructor生命周期钩子的最好实践是在这里初始化this.state

固然,你也可以运用属性初始化器来替代,以下:

import React, { Component } from 'react';

class App extends Component {
    state = {
        name: 'biu',
    };
}

export default App;

componentWillMount()

💀这是React不再引荐运用的API。

这是组件挂载到DOM之前的生命周期钩子。

很多人会有一个误区:这个钩子是要求数据然后将数据插进去元素一同挂载的最好时机。

实在componentWillMount和挂载是同步实行的,意味着实行完这个钩子,马上挂载。而向服务器要求数据是异步实行的。所以不管要求怎样快,都要排在同步任务今后再处置惩罚,这是辈份题目。

也就是说,永久不可以在这里将数据插进去元素一同挂载。

并不是说不能在这里要求数据,而是达不到你臆想的结果。

它被烧毁的缘由重要有两点:

  • 底本它就没什么用。预计当初是为了成双成对所以才制造了它吧。
  • 假如它声清楚明了定时器或许定阅器,在服务端衬着中,componentWillUnmount生命周期钩子中的消灭代码不会见效。因为假如组件没有挂载胜利,componentWillUnmount是不会实行的。姚明说的:没有挂载就没有卸载。
  • 在异步衬着中,它的表现不稳定。

初始化this.state应该在constructor生命周期钩子中完成,要求数据应该在componentDidMount生命周期钩子中完成,所以它不仅被烧毁了,连继任者都没有。

static getDerivedStateFromProps(props, state)

👽这是React v16.3.0宣布的API。

起首,这是一个静态要领生命周期钩子。

也就是说,定义的时刻得在要领前加一个static症结字,或许直接挂载到class类上。

扼要辨别一下实例要领和静态要领:

  • 实例要领,挂载在this上或许挂载在prototype上,class类不能直接接见该要领,运用new症结字实例化今后,实例可以接见该要领。
  • 静态要领,直接挂载在class类上,或许运用新的症结字static,实例没法直接接见该要领。

题目是,为何getDerivedStateFromProps生命周期钩子要设想成静态要领呢?

如许开发者就接见不到this也就是实例了,也就不能在里面挪用实例要领或许setsState了。

import React, { Component } from 'react';

class App extends Component {
    render() {
        return (
            <div>React</div>
        );
    }

    static getDerivedStateFromProps(props, state) {}
}

export default App;

这个生命周期钩子的任务是依据父组件传来的props按需更新本身的state,这类state叫做衍生state。返回的对象就是要增量更新的state。

它被设想成静态要领的目标是坚持该要领的地道,它就是用来定义衍生state的,除此以外不应该在里面实行任何操纵。

这个生命周期钩子也阅历了一些曲折,底本它是被设想成初始化父组件更新吸收到props才会触发,如今只需衬着就会触发,也就是初始化更新阶段都邑触发。

render()

作为一个组件,最中心的功用就是把元素挂载到DOM上,所以render生命周期钩子是一定会用到的。

render生命周期钩子怎样吸收模板呢?固然是你return给它。

然则不引荐在return之前写过量的逻辑,假如逻辑过量,可以封装成一个函数。

render() {
    // 这里可以写一些逻辑
    return (
        <div>
            <input type="text" />
            <button>click</button>
        </div>
    );
}

注重,万万不要在render生命周期钩子里挪用this.setState,因为this.setState会激发render,这下就没完没了了。主公,有内奸。

componentDidMount()

这是组件挂载到DOM今后的生命周期钩子。

这多是除了render以外最重要的生命周期钩子,因为这时刻组件的各方面都准备就绪,天地任你闯。

这就是社会哥,人狠话不多。

componentWillReceiveProps(nextProps)

💀这是React不再引荐运用的API。

componentWillReceiveProps生命周期钩子只要一个参数,更新后的props。

该声明周期函数可以在两种状况下被触发:

  • 组件吸收到了新的属性。
  • 组件没有收到新的属性,然则因为父组件从新衬着致使当前组件也被从新衬着。

初始化时并不会触发该生命周期钩子。

一样,因为Fiber机制的引入,这个生命周期钩子有可以会屡次触发。

shouldComponentUpdate(nextProps, nextState)

这个生命周期钩子是一个开关,推断是不是须要更新,重要用来优化机能。

有一个破例,假如开发者挪用this.forceUpdate强迫更新,React组件会疏忽这个钩子。

shouldComponentUpdate生命周期钩子默许返回true。也就是说,默许状况下,只需组件触发了更新,组件就一定会更新。React把推断的控制权给了开发者。

不过周密的React还供应了一个PureComponent基类,它与Component基类的区别是PureComponent自动完成了一个shouldComponentUpdate生命周期钩子。

关于组件来讲,只要状况发作转变,才须要从新衬着。所以shouldComponentUpdate生命周期钩子暴露了两个参数,开发者可以经由过程比较this.propsnextPropsthis.statenextState来推断状况到底有没有发作转变,再响应的返回true或false。

什么状况下状况没转变,却依旧触发了更新呢?举个例子:

父组件给子组件传了一个值,当父组件状况变化,即使子组件吸收到的值没有变化,子组件也会被迫更新。这显然是异常不合理的,React对此无计可施,只能看开发者的个人造化了。

import React, { Component } from 'react';
import Child from './Child';

class App extends Component {
    state = { name: 'React', star: 1 };

    render() {
        const { name, star } = this.state;
        return (
            <div>
                <Child name={name} />
                <div>{star}</div>
                <button onClick={this.handle}>click</button>
            </div>
        );
    }

    handle = () => {
        this.setState(prevState => ({ star: ++prevState.star }));
    }
}

export default App;
import React, { Component } from 'react';

class Child extends Component {
    render() {
        return <h1>{this.props.name}</h1>;
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props === nextProps) {
            return false;
        } else {
            return true;
        }
    }
}

export default Child;

同时要注重援用范例的坑。

下面这类状况,this.propsnextProps永久不可以相称。

import React, { Component } from 'react';
import Child from './Child';

class App extends Component {
    state = { name: 'React', star: 1 };

    render() {
        return (
            <div>
                <Child name={{ friend: 'Vue' }} />
                <div>{this.state.star}</div>
                <button onClick={this.handle}>click</button>
            </div>
        );
    }

    handle = () => {
        this.setState(prevState => ({ star: ++prevState.star }));
    }
}

export default App;
import React, { Component } from 'react';

class Child extends Component {
    render() {
        return <h1>{this.props.friend}</h1>;
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props === nextProps) {
            return false;
        } else {
            return true;
        }
    }
}

export default Child;

解决要领有两个:

  • 比较this.props.xxxnextProps.xxx
  • 在父组件用一个变量将援用范例缓存起来。

所以this.statenextState是只能用第一种要领比较了,因为React每次更新state都邑返回一个新对象,而不是修正原对象。

componentWillUpdate(nextProps, nextState)

💀这是React不再引荐运用的API。

shouldComponentUpdate生命周期钩子返回true,或许挪用this.forceUpdate今后,会马上实行该生命周期钩子。

要特别注重,componentWillUpdate生命周期钩子每次更新前都邑实行,所以在这里挪用this.setState异常风险,有可以会没完没了。

一样,因为Fiber机制的引入,这个生命周期钩子有可以会屡次挪用。

getSnapshotBeforeUpdate(prevProps, prevState)

👽这是React v16.3.0宣布的API。

望文生义,保留状况快照用的。

它会在组件行将挂载时挪用,注重,是行将挂载。它以至挪用的比render还晚,因而可知render并没有完成挂载操纵,而是举行构建笼统UI的事情。getSnapshotBeforeUpdate实行完就会马上挪用componentDidUpdate生命周期钩子。

它是做什么用的呢?有一些状况,比方网页转动位置,我不须要它耐久化,只须要在组件更新今后可以恢复本来的位置即可。

getSnapshotBeforeUpdate生命周期钩子返回的值会被componentDidUpdate的第三个参数吸收,我们可以运用这个通道保留一些不须要耐久化的状况,用完即可舍弃。

很显然,它是用来庖代componentWillUpdate生命周期钩子的。

意义就是说呀,开发者平常用不到它。

componentDidUpdate(nextProps, nextState, snapshot)

这是组件更新今后触发的生命周期钩子。

搭配getSnapshotBeforeUpdate生命周期钩子运用的时刻,第三个参数是getSnapshotBeforeUpdate的返回值。

一样的,componentDidUpdate生命周期钩子每次更新后都邑实行,所以在这里挪用this.setState也异常风险,有可以会没完没了。

componentWillUnmount()

这是组件卸载之前的生命周期钩子。

为何组件将近卸载了还须要寻思时刻呢?

因为开发者要擦屁股吖。

React的最好实践是,组件中用到的事宜监听器、定阅器、定时器都要在这里烧毁。

固然我说的事宜监听器指的是这类:

componentDidMount() {
    document.addEventListener('click', () => {});
}

因为下面这类React会自动烧毁,不劳烦开发者了。

render(
    return (
        <button onClick={this.handle}>click</button>
    );
)

componentDidCatch(error, info)

👽这是React v16.3.0宣布的API。

它重要用来捕捉毛病并举行响应处置惩罚,所以它的用法也比较特别。

定制一个只要componentDidCatch生命周期钩子的ErrorBoundary组件,它只做一件事:假如捕捉到毛病,则显现毛病提醒,假如没有捕捉到毛病,则显现子组件。

将须要捕捉毛病的组件作为ErrorBoundary的子组件衬着,一旦子组件抛出毛病,全部运用依旧不会崩溃,而是被ErrorBoundary捕捉。

import React, { Component } from 'react';

class ErrorBoundary extends Component {
    state = { hasError: false };

    render() {
        if (this.state.hasError) {
            return <h1>Something went wrong.</h1>;
        }
        return this.props.children;
    }

    componentDidCatch(error, info) {
        this.setState({ hasError: true });
    }
}

export default ErrorBoundary;
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import MyWidget from './MyWidget';

const App = () => {
    return (
        <ErrorBoundary>
            <MyWidget />
        </ErrorBoundary>
    );
}

export default App;

生命周期

这么多生命周期钩子,实际上总结起来只要三个历程:

  • 挂载
  • 更新
  • 卸载

挂载和卸载只会实行一次,更新会实行屡次。

一个完全的React组件生命周期会顺次挪用以下钩子:

old lifecycle

  • 挂载

    • constructor
    • componentWillMount
    • render
    • componentDidMount
  • 更新

    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 卸载

    • componentWillUnmount

new lifecycle

  • 挂载

    • constructor
    • getDerivedStateFromProps
    • render
    • componentDidMount
  • 更新

    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载

    • componentWillUnmount

组件树生命周期挪用栈

运用首次挂载时,我们以rendercomponentDidMount为例,React起首会挪用根组件的render钩子,假如有子组件的话,顺次挪用子组件的render钩子,挪用历程实在就是递归的递次。

等一切组件的render钩子都递归实行终了,这时刻实行权在末了一个子组件手里,因而最先触发下一轮生命周期钩子,挪用末了一个子组件的componentDidMount钩子,然后挪用栈顺次往上递归。

组件树的生命周期挪用栈走的是一个Z字形。

假如根组件没有定义A生命周期钩子而子组件定义了,那挪用栈就从这个子组件的A生命周期钩子最先。

别的,只需组件内定义了某个生命周期钩子,即使它没有任何行动,也会实行。

app.render();
child.render();
grandson.render();
// divide
grandson.componentDidMount();
child.componentDidMount();
app.componentDidMount();
// divide
app.render();
child.render();
grandson.render();
// divide
grandson.componentDidUpdate();
child.componentDidUpdate();
app.componentDidUpdate();

固然,componentWillMount、componentWillReceiveProps和componentWillUpdate生命周期钩子有可以被打断实行,也有可以被屡次挪用,表现是不稳定的。所以React决议逐步烧毁它们。

不过相识全部运用生命周期的一般挪用递次,照样有助于明白React的。

React专题一览

什么是UI
JSX
可变状况
不可变属性
生命周期
组件
事宜
操纵DOM
笼统UI

    原文作者:JsTheGreat
    原文地址: https://segmentfault.com/a/1190000016116388
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞