iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 19
1
Modern Web

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

【React.js入門 - 19】 React生命週期(4/4): Update系列一次講完

  • 分享至 

  • xImage
  •  

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


在前面的文章,我們一直提到在state或props被改變時會觸發re-render程序。
那這個神祕的程序是什麼? 就是React update的生命週期。

在Ver.17前,React update的生命週期是這樣的:

  1. (如果是props被改變才執行) componentWillReceiveProps(nextProps)
  2. shouldComponentUpdate(nextProps, nextState)
  3. componentWillUpdate(nextProps, nextState)
  4. render()
  5. 渲染畫面
  6. componentDidUpdate(prevProps, prevState)

Ver.17後,componentWillReceiveProps(nextProps)componentWillUpdate(nextProps, nextState)componentWillMount一樣被改為unsafe_(可參考此篇)。

而Ver.16.3所誕生的新的生命週期是:

  1. static getDerivedStateFromProps(props, state)
  2. shouldComponentUpdate(nextProps, nextState)
  3. render()
  4. getSnapshotBeforeUpdate()
  5. 渲染畫面
  6. componentDidUpdate(prevProps, prevState)

注意render()前從3變成只剩2個函數,render()後從1變成2個函數。

因為舊的函數有些不太常用到,在這邊我們直接來講新的函數。

static getDerivedStateFromProps(props, state)

對,跟mount中的那一個是一樣、共用的。比較特別的是因為它的功能是「從props中取得state要設定的值」,在React16.3版本中只有props被改變時才會觸發,state被改變時並不會。但React16.4版本後這個函數被改為只要re-render時此函數就會被觸發(2020/02/06更新)。在這裡,參數的propsstate是更新過後的。

注意這個函數是static,也就是this是不能使用的。
(static指的是這函式不屬於以這個類別被宣告出來的單一物件,而是只屬於這個類別)

shouldComponentUpdate(nextProps, nextState)

這個函數的功用像是守門員,用來做確認是不是真的要update。這個函數要return一個布林值。當函數回傳false時,元件就不會更新,也不會繼續執行接下來的render()以及剩下的update生命週期函數。預設會回傳true

在這邊,this.propsthis.state是更新之前的,新的props和state在參數中以nextPropsnextState存在。你可以在這裡對這四者做比較。

render

和前面一樣、共用,不解釋...。

getSnapshotBeforeUpdate(prevProps, prevProps)

來,在妳離開我們前,拍張照片吧!

這個函式夾在「render()收集完要更新的東西」跟「React真的拿render()中的東西去更新DOM」之間。它的用途是讓你可以把更新前的最後一刻DOM的狀況紀錄下來,然後用return值傳參數到 componentDidUpdate中。所以如果沒有要傳東西給 componentDidUpdate,就要回傳null

在這邊和前面相反this.propsthis.state是更新之後的,舊的props和state在參數中以prevPropsprevProps存在。

getSnapshotBeforeUpdate(prevProps, prevState)不常使用,不過可以參考一下官方的範例。

componentDidUpdate(prevProps, prevState, snapshot)

componentDidMount一樣,這個函數被強烈建議把「更新後想做的事情」放在這裡,包括先前提過的fetch等。因為這個函數是唯一也是最後在DOM真的被更新後執行的週期函數

在這邊this.propsthis.state是更新之後的,舊的props和state在參數中以prevPropsprevProps存在,snapshotgetSnapshotBeforeUpdate(prevProps, prevProps)傳來的參數。

使用這個函數時有一個很重要的事情: 只要任何一個props/state被設定(assign),就會重新進入Update週期,導致此函數再次被執行。為什麼這件事很重要呢? 假設今天有一個props叫A,一個state叫B,我們希望A被改變為true時,給予B一個值:

componentDidUpdate(prevProps, prevState, snapshot){
    if(this.props.A===true)
        this.setState({B: "A是真的!"})
}

實際執行你會發現,react在A第一次被設定後,開始瘋狂的重複設定B。然後你的網頁就死掉了。

為什麼會這樣? 因為你在componentDidUpdate中設定stateprops時,又會進入re-render的update生命週期,也就是進入像這樣的無限循環:

偵測到A被設定-> 在componentDidUpdate中設定B -> 偵測到B被設定 -> 在componentDidUpdate中設定B -> 偵測到B被設定 -> ...

如果要避免無限循環,就要改成這樣:

componentDidUpdate(prevProps, prevState, snapshot){
    if(this.props.A===true && prevProps.A != this.props.A)
        this.setState({B: "A是真的!"})
}

偵測到A被設定-> 在componentDidUpdate中設定B -> 偵測到B被設定 -> 發現A的值和之前一樣 -> 不設定B

而這邊不用prevState.B != this.state.B是因為會變成這樣

偵測到A被設定-> 在componentDidUpdate中設定B -> 偵測到B被設定 -> 在componentDidUpdate中設定B -> 偵測到B被設定 -> 發現B的值和之前一樣 -> 不設定B

以上是常用的處理方法,當然要視遇到的狀況作調整。

最後 - 流程圖終於來了

這是外國一位工程師(React開發者之一)做的React生命週期流程圖,搭配這張圖,應該會對這幾篇有比較具體的了解:

在Update週期中,最需要注意的就是「如果沒有使用週期函數中提供的修改方法,只要任何一個props/state被更新就會re-render」這件事。很容易一不小心就segmentation fault(記憶體分配失敗,為無限迴圈常導致的錯誤)

生命週期雖然很多,但你不需要每個都記住,有印象就可以,只需要特別記得我們最常用到的三個週期函數componentDidMountcomponentWillUnmountcomponentDidUpdate,可以參考這個網站,它整理了新/舊/常用/不常用的生命週期圖,還有多國語言翻譯,相當讚。

截自此網站:

小結

到這篇,三大React生命週期都已經講完了,另外React還有一個處理error用的週期,由於這系列只是入門就不特別講了。有興趣可以參考官方文件

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

下一篇,我們會來講在function component中生命週期的使用。


上一篇
【React.js入門 - 18】 React生命週期(3/4): Unmount - 只有componentWillUnmount
下一篇
【React.js入門 - 20】 useEffect - 在function component用生命週期
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
huli
iT邦新手 3 級 ‧ 2019-09-30 16:11:37

你文中裡面提到的「外國一位工程師」,是 React 的開發者以及 Redux 的原作者XD

Andy Chang iT邦研究生 4 級 ‧ 2019-09-30 16:59:58 檢舉

……我太孤陋寡聞了

Ashe Li iT邦新手 5 級 ‧ 2019-09-30 20:48:53 檢舉

Dan Abramov
@dan_abramov

Andy Chang iT邦研究生 4 級 ‧ 2019-09-30 22:29:32 檢舉

Ashe Li感謝你!

其實我知道這張是從Dan Abramov的twitter發的,但我沒有特別去查Dan Abramov是誰,只是短暫翻了他的twitter的關注議題就引用這張了。

這也是我自己要改進的地方。居然鬧這種笑話嗚嗚嗚/images/emoticon/emoticon02.gif

0
kiancaca
iT邦新手 5 級 ‧ 2020-02-06 14:01:59

Hi,

我從 React: Updating 的理解,getDerivedStateFromProps() 不論 propsstate 改變就會觸發。
我用 setState 實驗也的確會觸發,不像文中提到 只有props被改變時才會觸發,state被改變時並不會。

想進一步確認這部份。

Andy Chang iT邦研究生 4 級 ‧ 2020-02-06 15:34:34 檢舉

Hi kiancaca:
謝謝你的提醒!我查了一下,這是在React 16.3到React 16.4的更動,有關getDerivedStateFromProps()我在文中提到的

只有props被改變時才會觸發,state被改變時並不會

是在React 16.3的狀況,此情形已在React 16.4更新後改為你所說的

getDerivedStateFromProps() 不論 props 或 state 改變就會觸發。

我會在文中馬上補上附註,也謝謝你讓我注意到這件事!
參考資料:

  1. React 16.4更新紀錄 ( May 23, 2018 )
  2. 這篇stackoverflow
  3. React v15到v16.3, v16.4新生命週期總結以及使用場景
kiancaca iT邦新手 5 級 ‧ 2020-02-07 03:01:27 檢舉

原來是版本的差別!謝謝你!

我要留言

立即登入留言