React 元件生命週期詳解

暖生發表於2019-01-21

本文詳細介紹了 React 生命週期的用法以及各個階段的生命週期進行,並且用例項程式碼做了詳細演示。程式碼位置

話不多說,先上圖

React生命週期圖解

上圖是基於 React 16.4 之後的生命週期圖解。如感覺不對,請先檢視 React 版本


React 生命週期詳解

各個階段的生命週期函式

constructor 建構函式

在 React 元件掛載之前被呼叫,實現 React.Component 的子類的建構函式時,要在第一行加上 super(props)。

React 建構函式通常只用於兩個目的:

  • 通過分配一個物件到 this.state 來初始化本地 state
  • 將 事件處理程式 方法繫結到例項

如果沒有初始化狀態(state),並且沒有繫結方法,通常不需要為 React 元件實現一個建構函式。

不需要在建構函式中呼叫 setState(),只需將初始狀態設定給 this.state 即可 。

static getDerivedStateFromProps(nextProps, prevState)

getDerivedStateFromProps 在每次呼叫 render 方法之前呼叫。包括初始化和後續更新時。

包含兩個引數:第一個引數為即將更新的 props 值,第二個引數為之前的 state

返回值:返回為 null 時,不做任何副作用處理。倘若想更新某些 state 狀態值,則返回一個物件,就會對 state 進行修改

該生命週期是靜態函式,屬於類的方法,其作用域內是找不到 this

render()

render() 方法是類元件中唯一必須的方法,其餘生命週期不是必須要寫。 元件渲染時會走到該生命週期,展示的元件都是由 render() 生命週期的返回值來決定。

注意: 如果 shouldComponentUpdate() 方法返回 false ,render() 不會被呼叫。

componentDidMount()

在 React 元件裝載(mounting)(插入樹)後被立即呼叫。

componentDidMount 生命週期是進行傳送網路請求、啟用事件監聽的好時機

如果有必要,可以在此生命週期中立刻呼叫 setState()

shouldComponentUpdate(nextProps, nextState)

在元件準備更新之前呼叫,可以控制元件是否進行更新,返回 true 時元件更新,返回 false 元件不更新。

包含兩個引數,第一個是即將更新的 props 值,第二個是即將跟新後的 state 值,可以根據更新前後的 props 或 state 進行判斷,決定是否更新,進行效能優化

不要 shouldComponentUpdate 中呼叫 setState(),否則會導致無限迴圈呼叫更新、渲染,直至瀏覽器記憶體崩潰

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次的渲染輸出被提交之前呼叫。也就是說,在 render 之後,即將對元件進行掛載時呼叫。

它可以使元件在 DOM 真正更新之前捕獲一些資訊(例如滾動位置),此生命週期返回的任何值都會作為引數傳遞給 componentDidUpdate()。如不需要傳遞任何值,那麼請返回 null

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate() 在更新發生之後立即被呼叫。這個生命週期在元件第一次渲染時不會觸發。

可以在此生命週期中呼叫 setState(),但是必須包含在條件語句中,否則會造成無限迴圈,最終導致瀏覽器記憶體崩潰

componentWillUnmount()

componentWillUnmount() 在元件即將被解除安裝或銷燬時進行呼叫。

此生命週期是取消網路請求、移除監聽事件清理 DOM 元素清理定時器等操作的好時機

注意: componentWillMount()、componentWillUpdate()、componentWillReceiveProps() 即將被廢棄,請不要再在元件中進行使用。因此本文不做講解,避免混淆。

生命週期執行順序

掛載時

  • constructor()

  • static getDerivedStateFromProps()

  • render()

  • componentDidMount()

更新時

  • static getDerivedStateFromProps()

  • shouldComponentUpdate()

  • render()

  • getSnapshotBeforeUpdate()

  • componentDidUpdate()

解除安裝時

  • componentWillUnmount()

生命週期中是否可呼叫 setState()

初始化 state

  • constructor()

可以呼叫 setState()

  • componentDidMount()

根據判斷條件可以呼叫 setState()

  • componentDidUpdate()

禁止呼叫 setState()

  • shouldComponentUpdate()

  • render()

  • getSnapshotBeforeUpdate()

  • componentWillUnmount()


例項演示

原始碼地址

下面根據一個父子元件的props 改變、state 改變以及子元件的掛載/解除安裝等事件,對各生命週期執行順序進行理解,有興趣的同學可以一起看一下,也可以下載程式碼自己進行測試。

編寫元件程式碼

父元件:Parent.js

import React, { Component } from 'react';

import Child from './Child.js';

const parentStyle = {
    padding: 40,
    margin: 20,
    border: '1px solid pink'
}

const TAG = "Parent 元件:"

export default class Parent extends Component {

    constructor(props) {
        super(props);
        console.log(TAG, 'constructor');
        this.state = {
            num: 0,
            mountChild: true
        }
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        console.log(TAG, 'getDerivedStateFromProps');
        return null;
    }

    componentDidMount() {
        console.log(TAG, 'componentDidMount');
    }

    shouldComponentUpdate(nextProps, nextState) {
        console.log(TAG, 'shouldComponentUpdate');
        return true;
    }

    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log(TAG, 'getSnapshotBeforeUpdate');
        return null;
    }
    
    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log(TAG, 'componentDidUpdate');
    }

    componentWillUnmount() {
        console.log(TAG, 'componentWillUnmount');
    }


    /**
     * 修改傳給子元件屬性 num 的方法
     */
    changeNum = () => {
        let { num } = this.state;
        this.setState({
            num: ++ num
        });
    }

    /**
     * 切換子元件掛載和解除安裝的方法
     */
    toggleMountChild = () => {
        let { mountChild } = this.state;
        this.setState({
            mountChild: !mountChild
        });
    }

    render() {
        console.log(TAG, 'render');
        const { num, mountChild } = this.state;
        return (
            <div style={ parentStyle }>
                <div>
                    <p>父元件</p>
                    <button onClick={ this.changeNum }>改變傳給子元件的屬性 num</button>
                    <br />
                    <br />
                    <button onClick={ this.toggleMountChild }>解除安裝 / 掛載子元件</button>
                </div>
                {
                    mountChild ? <Child num={ num } /> : null
                }
            </div>
        )
    }
}
複製程式碼

子元件:Child.js

import React, { Component } from 'react'


const childStyle = {
    padding: 20,
    margin: 20,
    border: '1px solid black'
}

const TAG = 'Child 元件:'

export default class Child extends Component {

    constructor(props) {
        super(props);
        console.log(TAG, 'constructor');
        this.state = {
            counter: 0
        };
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        console.log(TAG, 'getDerivedStateFromProps');
        return null;
    }

    componentDidMount() {
        console.log(TAG, 'componentDidMount');
    }

    shouldComponentUpdate(nextProps, nextState) {
        console.log(TAG, 'shouldComponentUpdate');
        return true;
    }

    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log(TAG, 'getSnapshotBeforeUpdate');
        return null;
    }
    
    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log(TAG, 'componentDidUpdate');
    }

    componentWillUnmount() {
        console.log(TAG, 'componentWillUnmount');
    }

    changeCounter = () => {
        let { counter }= this.state;
        this.setState({
            counter: ++ counter
        });
    }

    render() {
        console.log(TAG, 'render');
        const { num } = this.props;
        const { counter } = this.state;
        return (
            <div style={ childStyle }>
                <p>子元件</p>
                <p>父元件傳過來的屬性 num : { num }</p>
                <p>自身狀態 counter : { counter }</p>
                <button onClick={ this.changeCounter }>改變自身狀態 counter</button>
            </div>
        )
    }
}
複製程式碼

從五種元件狀態改變的時機來探究生命週期的執行順序

一、父子元件初始化

父子元件第一次進行渲染載入時,介面展示為:

初始化展示介面

控制檯中的 log 列印順序為:

  • Parent 元件: constructor()
  • Parent 元件: getDerivedStateFromProps()
  • Parent 元件: render()
  • Child 元件: constructor()
  • Child 元件: getDerivedStateFromProps()
  • Child 元件: render()
  • Child 元件: componentDidMount()
  • Parent 元件: componentDidMount()

二、修改子元件自身狀態 state 時

點選子元件中的 改變自身狀態 按鈕,則介面上 自身狀態 counter: 的值會 + 1,控制檯中的 log 列印順序為:

  • Child 元件: getDerivedStateFromProps()
  • Child 元件: shouldComponentUpdate()
  • Child 元件: render()
  • Child 元件: getSnapshotBeforeUpdate()
  • Child 元件: componentDidUpdate()

三、修改父元件中傳入子元件的 props 時

點選父元件中的 改變傳給子元件的屬性 num 按鈕,則介面上 父元件傳過來的屬性 num: 的值會 + 1,控制檯中的 log 列印順序為:

  • Parent 元件: getDerivedStateFromProps()
  • Parent 元件: shouldComponentUpdate()
  • Parent 元件: render()
  • Child 元件: getDerivedStateFromProps()
  • Child 元件: shouldComponentUpdate()
  • Child 元件: render()
  • Child 元件: getSnapshotBeforeUpdate()
  • Parent 元件: getSnapshotBeforeUpdate()
  • Child 元件: componentDidUpdate()
  • Parent 元件: componentDidUpdate()

四、解除安裝子元件

點選父元件中的 解除安裝 / 掛載子元件 按鈕,則介面上子元件會消失,控制檯中的 log 列印順序為:

  • Parent 元件: getDerivedStateFromProps()
  • Parent 元件: shouldComponentUpdate()
  • Parent 元件: render()
  • Parent 元件: getSnapshotBeforeUpdate()
  • Child 元件: componentWillUnmount()
  • Parent 元件: componentDidUpdate()

五、重新掛載子元件

再次點選父元件中的 解除安裝 / 掛載子元件 按鈕,則介面上子元件會重新渲染出來,控制檯中的 log 列印順序為:

  • Parent 元件: getDerivedStateFromProps()
  • Parent 元件: shouldComponentUpdate()
  • Parent 元件: render()
  • Child 元件: constructor()
  • Child 元件: getDerivedStateFromProps()
  • Child 元件: render()
  • Parent 元件: getSnapshotBeforeUpdate()
  • Child 元件: componentDidMount()
  • Parent 元件: componentDidUpdate()

父子元件生命週期執行順序總結:

  • 當子元件自身狀態改變時,不會對父元件產生副作用的情況下,父元件不會進行更新,即不會觸發父元件的生命週期

  • 當父元件中狀態發生變化(包括子元件的掛載以及)時,會觸發自身對應的生命週期以及子元件的更新

    • render 以及 render 之前的生命週期,則 父元件 先執行

    • render 以及 render 之後的宣告週期,則子元件先執行,並且是與父元件交替執行

  • 當子元件進行解除安裝時,只會執行自身的 componentWillUnmount 生命週期,不會再觸發別的生命週期

可能總結的不好,不是很完整。只是根據一般情況進行的總結。有不妥之處,希望各位朋友能夠多多指正。


示例程式碼下載

原始碼地址(歡迎 Star,謝謝!)

還沒看夠?移步至:React Component 官網

相關文章