React 16.3 之後的Lifecycle hooks

pxn
6 min readApr 11, 2018

--

16.3最大的變革之一是有三個Lifecycle hook被列為不安全,分別為:

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

目前16.3這些都還能正常觸發,另外為了慢慢地淘汰掉這些hook,此版開始這三個hook會有 UNSAFE_ 前綴的別名,此外加入了新的hook來對應未來的改版,React團隊預計在17.0版淘汰掉這三個hook,僅留下UNSAFE_前綴作為備援,官方推薦盡量改用新的hook來喜迎17版

在開幹之前…

我知道聽到這個消息你可能第一時間會覺得錯愕,專案裡已經不知道用了多少(使用 x 誤用 o)想到要逐一改就頭大,不過在開幹之前可以先看看Dan神在JSConf Iceland 2018的Demo

前半段演算效能的火力展示可能勾不起前端人太大興趣,不過後半段對於Async Rendering (suspense)的介紹讓我整個都燃起來了,透過新技術在實務上可以解決掉許多UI/UX痛點,讓我對17版抱持了非常大的期待

推出新的Lifecycle hook也是因為長久以來大家的錯誤使用Lifecycle,導致要實踐17版suspense這樣的新技術無法順利執行,所以為了炫砲的Async Rendering,該改的還是早點改一改別欠技術債吧

過來人經驗談

在官方部落格有提供詳細的升級說明 (原文: Update on Async Rendering) 這兩天動手修了手上兩個project,其實改動程度並沒有想像中艱鉅,先說結論…

componentWillMount 改接 componentDidMount
componentWillUpdate 改接 compomentDidUpdate

99%可以無痛改版

可是我需要在元件Mount前呼叫遠端資料阿…

官方開釋這是誤用,這在Server side render跟未來的Async Rendering會有衝突,因為componentWillMount很可能被呼叫很多次,安全的做法是改接
componentDidMount

至於Subscribe event listener, setInterval 之類的動作我一直都習慣用componentDidMount不用改,如果以前誤植接到componentWillMount的,也請一併修正

componentWillUpdate官方敘述的使用情境是

“在state mutated後將State傳到外部callback”

要改成compomentDidUpdate來接,這其實蠻合理的,如果同時間有許多setState被觸發,串接compomentDidUpdate才能確保是安定的 state,不過這個使用情境我幾乎沒遇到

componentWillReceiveProps

要改最多的應當是componentWillReceiveProps,在看到原文的時候就有這樣的預感,這個lifecycle hook 使用的機會很多,舉凡:

  • 根據props去更新內部state
  • props改變的時候呼叫外部function (包含fetch之類的async call)

先說第一種案例, 16.3引進了一個新的Hook:getDerivedStateFromProp,大體上是來解決componentWillReceiveProps在案例一的情境,他的method signature長這樣

static getDerivedStateFromProps(nextProps, prevState) {
// ...
}

要注意的是這個是 static method 所以是沒有this context的,this.props / this.state 這些都無法被引用 (並沒有綁到實體上),可做為參考的只有當作args傳進來的 nextProps, prevState

getDerivedStateFromProp 回傳的會是更新過後的state object 或是 null 表示state不需要更改

透過官方的範例可以得知,以往單靠比較 this.props (舊值) vs nextProps (新值)然後setState的作法已經行不通了,必須把要比較的值也存到state當中,才能在之後透過 nextProps.something vs prevState.something做比較

在有複數情況要透過props變化來update state,或是state本身複雜的時候,寫getDerivedStateFromProp要特別小心,注意回傳的state object並不會跟prevState merge,不像setState是會shallow merge的,我能給的小技巧建議就是善用es6的spread syntax (…) 做shallow merge 例如:

return {
...prevState,
isScrollDown: true
}

然後複數conditions情況下,我會這樣寫 (state沒有髒掉就回傳null)

let state = {...prevState}
let dirty = false
if(nextProps.foo !== prevState.foo){
state.foo = nextProps.foo
dirty = true
}
if(nextProps.bar!== prevState.bar){
state.bar = nextProps.bar
dirty = true
}
return dirty ? state : null

這樣案例一算是解決了,來看看案例二的情況,這有可能發生在傳進來的新props碰到某個條件,你可能要dispatch action去redux, router要跳轉阿之類的

基本上只要不牽涉到state需要改變,其實這些動作可以接到compomentDidUpdate來執行

上面的例子是假定透過redux發了個ajax request等著props更新,reqPending變化並且reqPending === false的時候表示收到response了,然後response沒有錯誤的話就透過 react-router 的 history api跳到別的 route

在compomentDidUpdate是可以存取this context 還有 prevProps, prevState 所以可以做各種比較,大體上只要不涉及到setState的步驟都可以在這邊做…

疑? 你說如果我要改state怎麼辦,同學,那個你在上一動getDerivedStateFromProp就該做好了阿… (茶)

Dan神有提供一張圖表來解釋各個Lifecycle hook的發生時序,看完應該會更明白整個生命週期

結語

除此之外還有一個新的 getSnapshotBeforeUpdate,理論上這個是來讀上一禎DOM的快照,主要拿來做一些高度調整樣式連動之類的改變,但這個我沒怎麼用到就不講了,總的來說變動不多啦,為了而後的17版強大功能,各位同學還是捏著____趕緊修一修改一改吧,Right On!

--

--

pxn

闖蕩矽谷十餘年的打工仔,目前是 Everbridge 的 UX Architect,如果有現成輪子就懶得自己動手的務實派,但沒有輪子的時候,就隨手造個火箭出來吧 🚀