16.3最大的變革之一是有三個Lifecycle hook被列為不安全,分別為:
- componentWillMount
- componentWillReceiveProps
- 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 = falseif(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!