????????React 官方正式發(fā)布了 v16.3 版本柑营。在這次的更新中陆淀,除了前段時(shí)間被熱烈討論的新 Context API之外慢味,新引入的兩個(gè)生命周期函數(shù)getDerivedStateFromProps璧诵,getSnapshotBeforeUpdate以及在未來 v17.0 版本中即將被移除的三個(gè)生命周期函數(shù)componentWillMount轴猎,componentWillReceiveProps莉钙,componentWillUpdate也非常值得我們花點(diǎn)時(shí)間去探究一下其背后的原因以及在具體項(xiàng)目中的升級方案廓脆。
componentWillMount
首屏無數(shù)據(jù)導(dǎo)致白屏
????????在 React 應(yīng)用中,許多開發(fā)者為了避免第一次渲染時(shí)頁面因?yàn)闆]有獲取到異步數(shù)據(jù)導(dǎo)致的白屏磁玉,而將數(shù)據(jù)請求部分的代碼放在了componentWillMount中停忿,希望可以避免白屏并提早異步請求的發(fā)送時(shí)間。但事實(shí)上在componentWillMount執(zhí)行后蚊伞,第一次渲染就已經(jīng)開始了席赂,所以如果在componentWillMount執(zhí)行時(shí)還沒有獲取到異步數(shù)據(jù)的話,頁面首次渲染時(shí)也仍然會處于沒有異步數(shù)據(jù)的狀態(tài)时迫。換句話說颅停,組件在首次渲染時(shí)總是會處于沒有異步數(shù)據(jù)的狀態(tài),所以不論在哪里發(fā)送數(shù)據(jù)請求掠拳,都無法直接解決這一問題癞揉。而關(guān)于提早發(fā)送數(shù)據(jù)請求,官方也鼓勵(lì)將數(shù)據(jù)請求部分的代碼放在組件的constructor中,而不是componentWillMount烧董。
????????另一個(gè)常見的componentWillMount的用例是在服務(wù)端渲染時(shí)獲取數(shù)據(jù)毁靶,因?yàn)樵诜?wù)端渲染時(shí)componentDidMount是不會被調(diào)用的。針對這個(gè)問題逊移,筆者這里提供兩種解法预吆。第一個(gè)簡單的解法是將所有的數(shù)據(jù)請求都放在componentDidMount中,即只在客戶端請求異步數(shù)據(jù)胳泉。這樣做可以避免在服務(wù)端和客戶端分別請求兩次相同的數(shù)據(jù)(componentWillMount在客戶端渲染時(shí)同樣會被調(diào)用到)拐叉,但很明顯的缺點(diǎn)就是無法在服務(wù)端渲染時(shí)獲取到頁面渲染所需的所有數(shù)據(jù),所以如果我們需要保證服務(wù)端返回的 HTML 就是用戶最終看到的 HTML 的話扇商,我們可以將每個(gè)頁面的數(shù)據(jù)獲取邏輯單獨(dú)抽離出來凤瘦,然后一一對應(yīng)到相應(yīng)的頁面,在服務(wù)端根據(jù)當(dāng)前頁面的路由找到相應(yīng)的數(shù)據(jù)請求案铺,利用鏈?zhǔn)降?Promise 在渲染最終的頁面前就將數(shù)據(jù)塞入 redux store 或其他數(shù)據(jù)管理工具中蔬芥,這樣服務(wù)端返回的 HTML 就是包含異步數(shù)據(jù)的結(jié)果了。
事件訂閱
????????另一個(gè)常見的用例是在componentWillMount中訂閱事件控汉,并在componentWillUnmount中取消掉相應(yīng)的事件訂閱笔诵。但事實(shí)上 React 并不能夠保證在componentWillMount被調(diào)用后,同一組件的componentWillUnmount也一定會被調(diào)用姑子。一個(gè)當(dāng)前版本的例子如服務(wù)端渲染時(shí)乎婿,componentWillUnmount是不會在服務(wù)端被調(diào)用的,所以在componentWillMount中訂閱事件就會直接導(dǎo)致服務(wù)端的內(nèi)存泄漏街佑。另一方面谢翎,在未來 React 開啟異步渲染模式后,在componentWillMount被調(diào)用之后沐旨,組件的渲染也很有可能會被其他的事務(wù)所打斷森逮,導(dǎo)致componentWillUnmount不會被調(diào)用。而componentDidMount就不存在這個(gè)問題希俩,在componentDidMount被調(diào)用后吊宋,componentWillUnmount一定會隨后被調(diào)用到,并根據(jù)具體代碼清除掉組件中存在的事件訂閱颜武。
升級方案
????????將現(xiàn)有?componentWillMount?中的代碼遷移至?componentDidMount?即可。
componentWillReceiveProps
更新由 props 決定的 state 及處理特定情況下的回調(diào)
????????在老版本的 React 中拖吼,如果組件自身的某個(gè) state 跟其 props 密切相關(guān)的話鳞上,一直都沒有一種很優(yōu)雅的處理方式去更新 state,而是需要在componentWillReceiveProps中判斷前后兩個(gè) props 是否相同吊档,如果不同再將新的 props 更新到相應(yīng)的 state 上去篙议。這樣做一來會破壞 state 數(shù)據(jù)的單一數(shù)據(jù)源,導(dǎo)致組件狀態(tài)變得不可預(yù)測,另一方面也會增加組件的重繪次數(shù)鬼贱。類似的業(yè)務(wù)需求也有很多移怯,如一個(gè)可以橫向滑動的列表,當(dāng)前高亮的 Tab 顯然隸屬于列表自身的狀態(tài)这难,但很多情況下舟误,業(yè)務(wù)需求會要求從外部跳轉(zhuǎn)至列表時(shí),根據(jù)傳入的某個(gè)值姻乓,直接定位到某個(gè) Tab嵌溢。
????????在新版本中,React 官方提供了一個(gè)更為簡潔的生命周期函數(shù):
? ??????????????????static getDerivedStateFromProps(nextProps, prevState)
?????????通常來講蹋岩,在componentWillReceiveProps中赖草,我們一般會做以下兩件事,一是根據(jù) props 來更新 state剪个,二是觸發(fā)一些回調(diào)秧骑,如動畫或頁面跳轉(zhuǎn)等。在老版本的 React 中扣囊,這兩件事我們都需要在componentWillReceiveProps中去做腿堤。而在新版本中,官方將更新 state 與觸發(fā)回調(diào)重新分配到了getDerivedStateFromProps與componentDidUpdate中如暖,使得組件整體的更新邏輯更為清晰笆檀。而且在getDerivedStateFromProps中還禁止了組件去訪問 this.props,強(qiáng)制讓開發(fā)者去比較 nextProps 與 prevState 中的值盒至,以確保當(dāng)開發(fā)者用到getDerivedStateFromProps這個(gè)生命周期函數(shù)時(shí)酗洒,就是在根據(jù)當(dāng)前的 props 來更新組件的 state,而不是去做其他一些讓組件自身狀態(tài)變得更加不可預(yù)測的事情枷遂。
升級方案
????????將現(xiàn)有componentWillReceiveProps中的代碼根據(jù)更新 state 或回調(diào)樱衷,分別在getDerivedStateFromProps及componentDidUpdate中進(jìn)行相應(yīng)的重寫即可,注意新老生命周期函數(shù)中prevProps酒唉,this.props矩桂,nextProps,prevState痪伦,this.state的不同侄榴。
componentWillUpdate
處理因?yàn)?props 改變而帶來的副作用
????????與componentWillReceiveProps類似,許多開發(fā)者也會在componentWillUpdate中根據(jù) props 的變化去觸發(fā)一些回調(diào)网沾。但不論是componentWillReceiveProps還是componentWillUpdate癞蚕,都有可能在一次更新中被調(diào)用多次,也就是說寫在這里的回調(diào)函數(shù)也有可能會被調(diào)用多次辉哥,這顯然是不可取的桦山。與componentDidMount類似攒射,componentDidUpdate也不存在這樣的問題,一次更新中componentDidUpdate只會被調(diào)用一次恒水,所以將原先寫在componentWillUpdate中的回調(diào)遷移至componentDidUpdate就可以解決這個(gè)問題会放。
在組件更新前讀取 DOM 元素狀態(tài)
????????另一個(gè)常見的componentWillUpdate的用例是在組件更新前,讀取當(dāng)前某個(gè) DOM 元素的狀態(tài)钉凌,并在componentDidUpdate中進(jìn)行相應(yīng)的處理咧最。但在 React 開啟異步渲染模式后,render 階段和 commit 階段之間并不是無縫銜接的甩骏,也就是說在 render 階段讀取到的 DOM 元素狀態(tài)并不總是和 commit 階段相同窗市,這就導(dǎo)致在componentDidUpdate中使用componentWillUpdate中讀取到的 DOM 元素狀態(tài)是不安全的,因?yàn)檫@時(shí)的值很有可能已經(jīng)失效了饮笛。
????????為了解決上面提到的這個(gè)問題咨察,React 提供了一個(gè)新的生命周期函數(shù):
? ??????????????getSnapshotBeforeUpdate(prevProps, prevState)
????????與componentWillUpdate不同,getSnapshotBeforeUpdate會在最終的 render 之前被調(diào)用福青,也就是說在getSnapshotBeforeUpdate中讀取到的 DOM 元素狀態(tài)是可以保證與componentDidUpdate中一致的摄狱。雖然getSnapshotBeforeUpdate不是一個(gè)靜態(tài)方法,但我們也應(yīng)該盡量使用它去返回一個(gè)值无午。這個(gè)值會隨后被傳入到componentDidUpdate中媒役,然后我們就可以在componentDidUpdate中去更新組件的狀態(tài),而不是在getSnapshotBeforeUpdate中直接更新組件狀態(tài)宪迟。
升級方案
????????將現(xiàn)有的componentWillUpdate中的回調(diào)函數(shù)遷移至componentDidUpdate酣衷。如果觸發(fā)某些回調(diào)函數(shù)時(shí)需要用到 DOM 元素的狀態(tài),則將對比或計(jì)算的過程遷移至getSnapshotBeforeUpdate次泽,然后在componentDidUpdate中統(tǒng)一觸發(fā)回調(diào)或更新狀態(tài)穿仪。
改變之后是不是發(fā)現(xiàn)變化很大,簡潔的多了; 以上純屬轉(zhuǎn)發(fā)意荤,僅供學(xué)習(xí)參考啊片,不足之處多多指教,程序猿努力學(xué)習(xí)中...