1. 程式人生 > 實用技巧 >React 【生命週期】三個階段生命週期函式、不同生命週期詳解、生命週期呼叫順序

React 【生命週期】三個階段生命週期函式、不同生命週期詳解、生命週期呼叫順序

目錄:

1. 三個階段生命週期函式

2. 不同生命週期詳解

     建立階段

     更新階段

     解除安裝階段

3. 生命週期呼叫順序

  元件在頁面上從建立到銷燬分為了不同的狀態,React 給出了讓我們可以呼叫的生命週期函式。

一、三個階段生命週期函式

  React 生命週期主要包含 3 個階段,分別是初始化階段、執行中階段、銷燬階段,也就是建立階段、更新階段、解除安裝階段。在 React 不同的生命週期裡會依次觸發不同的鉤子函式。

  下面來看 React 生命週期中主要有哪些鉤子函式。

  在建立階段會執行 Constructor 建構函式,此時會初始化 props 跟 state 值。在 Constructor 完成之後,會分別執行 componentWillMount、render、componentDidMount 這 3 個鉤子函式。當 React 執行完 componentDidMount 之後,應用被載入到頁面上。之後頁面進行互動操作時,就進入更新階段。

  在更新階段會依次觸發 componentWillReceiveProps、shouldComponentUpdate、render、componentDidUpdate,這些函式的觸發是一個迴圈的過程,每當有交互發生變動的時候,這些函式都會被依次呼叫。

  當將一個元件從頁面上移除的時候,就會觸發 componentWillUnmount 。

  下面分階段看生命週期函式都有哪些特性和使用方法。

二、不同生命週期詳解

1. 建立階段

  在建立階段,元件會被創建出來並且被載入到 DOM 中去。

  如果嚴格劃分建立階段的話,可以劃分為初始化階段、掛載階段。

  初始化階段是呼叫 Constructor 的階段,掛載階段就是執行 componentWillMount、render、componentDidMount 的階段。

建立階段 constructor

  一個類必須要有一個 constructor 方法,如果這個方法沒有被顯式定義,那麼,一個預設的 constructor 函式會被新增。

  一般,constructor 方法會返回一個例項物件,返回了例項物件之後我們可以在類函式中使用 this 關鍵字,在 constructor 中需要使用 super 方法呼叫基類的構造方法,也將父元件的 props 注入給子元件。

  在 constructor 中可以做元件的初始化工作,比如說可以初始化 state,在 constructor 中可以直接修改 state,使用 this.state 進行賦值,不能使用 this.setState 方法。

建立階段 componentWillMount

  在元件掛載到 DOM 前(UI渲染完成前),componentWillMount 會被呼叫而且只會被呼叫一次。此時,如果使用 this.setState 是不會引起重新渲染的

  更多時候,會把元件元素裡的內容提前到 constructor 中,所以在專案中很少使用到 componentWillMount 這個生命週期函式。

建立階段 render

  render 方法是一個類元件必須有的方法,render 方法會返回一個頂級的 react 元素。render 方法返回的並不是一個真正的 DOM 元素,它渲染的是 Dom Tree 的一個 React 物件,React 之所以效率高,是因為使用了虛擬的 Dom Tree。需要注意的是,不能在 render 方法裡去執行 this.setState。

建立階段 componentDidMount

  在第一次渲染後呼叫,當 React 應用執行 componentDidMount 時就證明 UI 渲染完成了,這個生命週期函式只執行一次,它被呼叫時已經渲染出真實 DOM 了,這個生命週期函式比較適合使用的場景向服務端獲取非同步的資料,也就是獲取一些外部資料資源。需要注意的是,當父元件執行 render 的時候,只有當所有的子元件都完成了建立,父元件才會完成渲染,然後執行 componentDidMount。

例 子

import React, { PureComponent } from 'react';
import Navbar from "./components/navbar"
import ListPage from './components/listPage'

class App extends PureComponent {
    constructor( props ){
        super(props)
        this.state = {
            listData : [
                {
                    id: 1,
                    name: '紅蘋果',
                    price: 2,
                    value: 4
                },
                {
                    id: 2,
                    name: '青蘋果',
                    price: 3,
                    value: 2
                },
            ]
        }
        console.log('App - constructor');
    }
    componentDidMount () {
        console.log('App - mounted');
    }
    handleDelete = (id) => {
        const listData = this.state.listData.filter( item => item.id !== id )
        this.setState({
            listData
        })
    }
    handleReset = () => {
        const _list = this.state.listData.map( item => {
            const _item = {...item}
            _item.value = 0
            return _item
        })
        this.setState({
            listData : _list
        })
    }
    handleIncrease = (id) => {
        const _data = this.state.listData.map( item => {
            if( item.id === id ){
                const _item = {...item}
                _item.value++
                return _item
            }else{
                return item
            }
        })
        this.setState({
            listData : _data
        })
    }
    handleDecrease = (id) => {
        const _data = this.state.listData.map( item => {
            if( item.id === id ){
                const _item = {...item}
                _item.value--
                if( _item.value < 0 ) _item.value = 0
                return _item
            }else{
                return item
            }
        })
        this.setState({
            listData : _data
        })
    }
    render() { 
        console.log('App - rendering');
        return(
            <>
                <Navbar 
                    onReset = {this.handleReset}
                    total = {this.state.listData.length}
                />
                <ListPage 
                    data = {this.state.listData}
                    handleDecrease = {this.handleDecrease}
                    handleIncrease = {this.handleIncrease}
                    handleDelete = {this.handleDelete}
                />
            </>
        )
    }
}
 
export default App;
App.js

在父元件 App.js 的 constructor 、componentDidMount、render 中加上標記。

在 App.js 的子元件 navbar.jsx 的 render 方法內加上標記 “Nav - rendering”

在 App.js 的子元件 listPage.jsx 的 render 方法內加上標記 “Page - rendering”

在 ListPage.jsx 的子元件 listItem.jsx 的 render 方法內加上標記 “Item - rendering”

控制檯:

  可以看到整個應用的載入過程,首先是父元件 App.js 執行 constructor,然後執行 render,然後執行 navbar.jsx 跟 listPage.jsx 的 render ,然後執行 listItem.jsx 的 render,當子元件都渲染完之後,父元件 App.js 才執行 componentDidMount,這時完成了整個應用程式的建立。

2. 更新階段

更新階段 componentWillReceiveProps

  元件接收到新 props 的時候呼叫 componentWillReceiveProps,可以在此對比新 props 和原來的 props,如果 props 發生變化,我們可以進行一些業務邏輯的操作。不過,現在不推薦使用這個方法了,因為有新的生命週期函式可以取代它。

更新階段 shouldComponentUpdate

  這個方法可以用於判斷是否要繼續執行 render 方法。它比較新傳入的 nextProps 、 nextState 跟當前的 props、state ,如果返回 true,元件將繼續執行更新,如果返回 false,會阻止元件更新,從而減少不必要的渲染,以達到效能優化的目的。但是,跟 shouldComponentUpdate 相比,更推薦使用 PureComponent 自動實現 ( shouldComponentUpdate 相關筆記:https://www.cnblogs.com/xiaoxuStudy/p/13350935.html#shouldComponentUpdate )

  shouldComponentUpdate 同時接受 props 跟 state,componentWillReceiveProps 只接受 props。

更新階段 componentDidUpdate

  componentDidUpdate 在每次 UI 更新時呼叫。這個方法接收 2 個引數:新傳入的 props 跟 state,可以在這裡和原來的資料進行對比,進行一些業務邏輯的處理。可以更新一些外部資料資源。

例 子

在上面例子的基礎上,在 listItem.jsx 上新增一個 componentDidUpdate 函式,在函式內部新增一個判斷,通過 value 對比新傳入的 props 跟之前的 props,如果不一樣的話,輸出 ”Item - Updated“。

import React, { PureComponent } from 'react';
import style from './listItem.module.css';
import classnames from 'classnames/bind'

const cls = classnames.bind(style);

class ListItem extends PureComponent {
    componentDidUpdate(nextProps){
        if(nextProps.data.value !== this.props.data.value){
            console.log('Item - Updated');
        }
    }
    render() { 
        console.log('Item - rendering');
        return ( 
            <div className="row mb-3">
                <div className="col-6 themed-grid-col">
                    <span className={ cls('title', 'list-title') }>
                        {this.props.data.name}
                    </span>
                </div>
                <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                <div className={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}>
                    <button 
                        onClick={()=>{this.props.onDecrease(this.props.data.id)}}  
                        type="button" className="btn btn-primary"
                    >-</button>
                    <span className={ cls('digital') }>{this.props.data.value}</span>
                    <button 
                        onClick={()=>{this.props.onIncrease(this.props.data.id)}}  
                        type="button" className="btn btn-primary"
                    >+</button>
                </div>
                <div className="col-2 themed-grid-col">¥{this.props.data.price * this.props.data.value}</div>
                <div className="col-1 themed-grid-col">
                    <button 
                        onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                        type="button" className="btn btn-danger btn-sm" 
                    >刪除
                    </button>
                </div>
            </div>
        );
    }
}

export default ListItem;
listItem.jsx

頁面表現:

  清空控制檯,然後點選 “+” 按鈕,可以看到當各個元件完成渲染,整個應用完成更新,最後執行的是 componentDidUpdate,只觸發了一次。根據商品的變化,可以向後端要一些新的資料,這是這個生命週期函式的應用場景。

3. 解除安裝階段

解除安裝階段 componentWillUnmount

  元件從 DOM 中移除之前立刻被呼叫 (比如說刪除購物車中的某個商品時) componentWillUnmount,這個生命週期函式可以用來做資源的釋放,比如說可以清理繫結的 timer。

例 子

在 listItem.jsx 中新增 componentWillUnmount 函式,當呼叫該函式輸出“Item - Deleted”。

import React, { PureComponent } from 'react';
import style from './listItem.module.css';
import classnames from 'classnames/bind'

const cls = classnames.bind(style);

class ListItem extends PureComponent {
    componentDidUpdate(nextProps){
        if(nextProps.data.value !== this.props.data.value){
            console.log('Item - Updated');
        }
    }
    componentWillUnmount () {
        console.log( 'Item - Deleted' );
    }
    render() { 
        console.log('Item - rendering');
        return ( 
            <div className="row mb-3">
                <div className="col-6 themed-grid-col">
                    <span className={ cls('title', 'list-title') }>
                        {this.props.data.name}
                    </span>
                </div>
                <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                <div className={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}>
                    <button 
                        onClick={()=>{this.props.onDecrease(this.props.data.id)}}  
                        type="button" className="btn btn-primary"
                    >-</button>
                    <span className={ cls('digital') }>{this.props.data.value}</span>
                    <button 
                        onClick={()=>{this.props.onIncrease(this.props.data.id)}}  
                        type="button" className="btn btn-primary"
                    >+</button>
                </div>
                <div className="col-2 themed-grid-col">¥{this.props.data.price * this.props.data.value}</div>
                <div className="col-1 themed-grid-col">
                    <button 
                        onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                        type="button" className="btn btn-danger btn-sm" 
                    >刪除
                    </button>
                </div>
            </div>
        );
    }
}

export default ListItem;
listItem.jsx

頁面表現:

  清空控制檯,點選“刪除”按鈕後,可以看到當前子元件會在刪除之前立刻呼叫 componentWillUnmount

三、圖解生命週期

(圖片來源:https://blog.hhking.cn/2018/09/18/react-lifecycle-change/ )

生命週期呼叫順序(舊)

生命週期呼叫順序(新)