(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
在前面的文章,我們一直提到在state或props被改變時會觸發re-render程序。
那這個神祕的程序是什麼? 就是React update的生命週期。
在Ver.17前,React update的生命週期是這樣的:
- (如果是props被改變才執行) componentWillReceiveProps(nextProps)
- shouldComponentUpdate(nextProps, nextState)
- componentWillUpdate(nextProps, nextState)
- render()
- 渲染畫面
- componentDidUpdate(prevProps, prevState)
Ver.17後,componentWillReceiveProps(nextProps)
、componentWillUpdate(nextProps, nextState)
和componentWillMount
一樣被改為unsafe_
(可參考此篇)。
而Ver.16.3所誕生的新的生命週期是:
- static getDerivedStateFromProps(props, state)
- shouldComponentUpdate(nextProps, nextState)
- render()
- getSnapshotBeforeUpdate()
- 渲染畫面
- componentDidUpdate(prevProps, prevState)
注意render()
前從3變成只剩2個函數,render()
後從1變成2個函數。
因為舊的函數有些不太常用到,在這邊我們直接來講新的函數。
對,跟mount中的那一個是一樣、共用的。比較特別的是因為它的功能是「從props中取得state要設定的值」,在React16.3版本中只有props被改變時才會觸發,state被改變時並不會。但在React16.4版本後這個函數被改為只要re-render時此函數就會被觸發(2020/02/06更新)。在這裡,參數的props
和state
是更新過後的。
注意這個函數是static
,也就是this
是不能使用的。
(static指的是這函式不屬於以這個類別被宣告出來的單一物件,而是只屬於這個類別)
這個函數的功用像是守門員,用來做確認是不是真的要update。這個函數要return一個布林值。當函數回傳false
時,元件就不會更新,也不會繼續執行接下來的render()
以及剩下的update生命週期函數。預設會回傳true
。
在這邊,this.props
和this.state
是更新之前的,新的props和state在參數中以nextProps
和nextState
存在。你可以在這裡對這四者做比較。
和前面一樣、共用,不解釋...。
來,在妳離開我們前,拍張照片吧!
這個函式夾在「render()
收集完要更新的東西」跟「React真的拿render()
中的東西去更新DOM」之間。它的用途是讓你可以把更新前的最後一刻DOM的狀況紀錄下來,然後用return值傳參數到 componentDidUpdate
中。所以如果沒有要傳東西給 componentDidUpdate
,就要回傳null
。
在這邊和前面相反,this.props
和this.state
是更新之後的,舊的props和state在參數中以prevProps
和prevProps
存在。
getSnapshotBeforeUpdate(prevProps, prevState)
不常使用,不過可以參考一下官方的範例。
和componentDidMount
一樣,這個函數被強烈建議把「更新後想做的事情」放在這裡,包括先前提過的fetch等。因為這個函數是唯一也是最後在DOM真的被更新後執行的週期函數。
在這邊this.props
和this.state
是更新之後的,舊的props和state在參數中以prevProps
和prevProps
存在,snapshot
是getSnapshotBeforeUpdate(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
中設定state
或props
時,又會進入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(記憶體分配失敗,為無限迴圈常導致的錯誤)
生命週期雖然很多,但你不需要每個都記住,有印象就可以,只需要特別記得我們最常用到的三個週期函數componentDidMount
、componentWillUnmount
和componentDidUpdate
,可以參考這個網站,它整理了新/舊/常用/不常用的生命週期圖,還有多國語言翻譯,相當讚。
截自此網站:
到這篇,三大React生命週期都已經講完了,另外React還有一個處理error用的週期,由於這系列只是入門就不特別講了。有興趣可以參考官方文件。
過去在看大家對於React的討論,最常被提到的就是生命週期這東西並不是很多人真正的了解。
坦白說,我也不認為自己熟悉這一塊,自己目前做過的專案也只有碰到部分的函數。如果有錯誤的地方,希望各位前輩能指點~
下一篇,我們會來講在function component中生命週期的使用。
你文中裡面提到的「外國一位工程師」,是 React 的開發者以及 Redux 的原作者XD
……我太孤陋寡聞了
Dan Abramov
@dan_abramov
Ashe Li感謝你!
其實我知道這張是從Dan Abramov的twitter發的,但我沒有特別去查Dan Abramov是誰,只是短暫翻了他的twitter的關注議題就引用這張了。
這也是我自己要改進的地方。居然鬧這種笑話嗚嗚嗚
Hi,
我從 React: Updating 的理解,getDerivedStateFromProps()
不論 props
或 state
改變就會觸發。
我用 setState 實驗也的確會觸發,不像文中提到 只有props被改變時才會觸發,state被改變時並不會。
想進一步確認這部份。
Hi kiancaca:
謝謝你的提醒!我查了一下,這是在React 16.3到React 16.4的更動,有關getDerivedStateFromProps()
我在文中提到的
只有props被改變時才會觸發,state被改變時並不會
是在React 16.3的狀況,此情形已在React 16.4更新後改為你所說的
getDerivedStateFromProps() 不論 props 或 state 改變就會觸發。
我會在文中馬上補上附註,也謝謝你讓我注意到這件事!
參考資料:
原來是版本的差別!謝謝你!