iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 16
0
Modern Web

給初入JS框架新手的React.js入門系列 第 16

【React.js入門 - 16】 React生命週期(1/4): Mount(上)- 在渲染以前

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


人的一生的週期,會從出生、長大到死亡。出生又可以分為胚胎期、生出來那一瞬間、生出來後,長大也是一樣可以繼續往下區分...。

生命週期分類

和人的一生很像,對於大部分的GUI框架,「改變(渲染)畫面」這個動作背後其實都有一套流程(週期)。而React設計了一套特別的component生命週期函數,在這三個情境建構了不同的生命週期:

元件被安裝時(Mount)、元件被更新時(Update)、元件被移除時(Unmount)

每個週期也是和出生一樣可以區分,用一或多個函式隔成不同階段。例如你可以定義元件安裝前、安裝後要做什麼事,就像你可能會希望孩子誕生前幫他買個嬰兒床,健康的誕生,然後要他生出來之後叫你一聲爸以滿足你多年的夢想(?)

蛤?你到底在說什麼? js丟到畫面上可以work不就好了嗎?為什麼安裝前要做事情?安裝完還要幹嘛?

這就是為什麼我把生命週期拆成4篇來講,我希望能針對常用的生命週期,去講他們是怎麼被使用,之後Day19會放一張統整的流程圖,等我發到那裡的時候可以去搭配使用。

請注意,這四篇所講的生命週期函數只能在class component使用。有關function component的生命週期使用之後會特別講

我們先從安裝開始講起。

當元件被安裝 - Mount系列

在第一次渲染之前的生命週期 - Version 16.3之前

前面有提過,React component渲染畫面前最後一個呼叫函式是render()函式。實際上在第一次渲染之前,React還會依序呼叫兩個函式: constructor()和componentWillMount()。也就是第一次render前的流程是:

constructor() -> componentWillMount() -> render() -> 渲染DOM ->......(渲染後的生命週期)

Version 16.3之後發生了一些變化

在使用了一段時間後,大家發現componentWillMount()產生了一些問題,不被建議使用。因此在version 17後,componentWillMount()將會被改為UNSAFE_componentWillMount(),以Ver.16.3誕生的新週期函數static getDerivedStateFromProps()來代替。

也就是新的Mount週期在render前的流程是為:

constructor() -> static getDerivedStateFromProps() -> render() -> 渲染DOM -> ......(渲染後的生命週期)

在這篇,我還是會講一下有關componentWillMount()的使用。一者是因為目前很多網路上的資源還是以舊的生命週期為主,避免讀者在查相關資料時疑惑。另外一個原因是在我撰寫這篇的當下新舊生命函數都是可以使用的,且componentWillMount()未來也會以UNSAFE_componentWillMount()形式存在。

constructor()

這個比較不用特別說,過去class component就已經使用過了,就是資料的宣告、初始化、預備、函式綁定等等等......

getDerivedStateFromProps(props,state)

因為state和props在constructor才被創造,這個函數最常使用的狀況是「用初始接收到的props」去設定第一次render時的state或是做其他的事情。注意這個函數是static,也就是this不能在這裡使用(static指的是這函式不屬於以這個class被宣告出來的單一物件,而是泛屬於此class類別)。

因此props和state改用此函式接收的參數去讀取,並且我們不能在這邊呼叫this.setState更改state的方法是用預寫好的規則 : 以這個函式的return值來設定

  static getDerivedStateFromProps(props,state){
      if(props.dad!=="Chang")
        return {isRightDad:false}
  }

在這裡如果我們直接或間接對state/props更改,不會因為state/props改變而重複呼叫render函式。在這邊,我建立了一個Baby.js來說明:

import React, { Component } from 'react';
class Baby extends Component{
  constructor(props) {
    super(props);
    this.state={
        isRightDad: true
    }
  }

  static getDerivedStateFromProps(props,state){
      if(props.dad!=="Chang")
        return {isRightDad:false}
  }

    render(){
        if(this.state.isRightDad===true)
            return(
                <div>
                    他的媽媽,是小美
                </div>
            );
        else
            return(
                <div>
                    他的媽媽,是誰,干你X事
                </div>
            );

    }
}
export default Baby;

引入在index.js中

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Baby from './Baby'
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
    <div>
        <Baby dad="Chang"/>
    </div>,
    document.getElementById('root')
);

serviceWorker.unregister();

如果初始時得知爸爸不是張先生,把isRightDad設定為false,render不會告訴他孩子的媽是誰。

要注意的是,我們不希望在getDerivedStateFromProps()中做宣告/初始化的動作,如果可以的話就在constructor中做,其他如fetch或是動畫等,應該移到下一篇要講的componentDidMount()中來做。

<即將被移除>componentWillMount

它不是static,在裡面定義、使用函式方法跟一般函式差不多。

componentWillMount(){
      if(this.props.dad!=="Chang")
        this.setState({isRightDad:false})
  }

過去除了getDerivedStateFromProps()的功能外,很多人會在這裡執行fetch以取得想在render()中使用的資料。例如token的檢查等等。

聽起來「在這裡執行fetch,等資料接到了再render」很合理。然而官方表示,如果在componentWillMount()這執行fetch,並不會等response進來才執行 render。又因為這是唯一會在 *server side (見註解)執行的生命週期函數,導致它在server side和client side都會執行一次,「重複執行」這件事並不符合我們對Mount週期函數的期待。

version 17後,componentWillMount()將會被改為UNSAFE_componentWillMount()

*註解: 推薦看這兩篇來了解SSR(server side rendering)
(SSR) Server-Side Rendering with Angular
[React] SSR 筆記

render()

就是收集要渲染到畫面上的東西,前面都用過了。

要注意的是render()只是渲染前最後一個呼叫的生命週期函數,元件還沒有真的渲染到DOM上。所以不要在render()中操作有關return元素的DOM。

小結

一般我們對Mount系列函數的期待是「只執行一次」,不想重複執行的動作都會在這系列呼叫。

那麼從上一篇一直講到現在,fetch到底應該要在哪裡執行呢?
答案就在下一篇要講的componentDidMount()

過去在看大家對於React的討論,最常被提到的就是生命週期這東西並不是很多人真正的了解。
坦白說,我也不認為自己很熟悉這一塊,自己目前做過的專案也只有碰到部分的函數。如果有錯誤的地方,希望各位前輩能指點~


上一篇
【React.js入門 - 15】 使用Http request - Fetch Api
下一篇
【React.js入門 - 17】 React生命週期(2/4): Mount(下) - 應該多用的componentDidMount
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言