2019-6-12 seo達人
如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點這里
前幾天面試問道 react 的相關(guān)知識,對我打擊比較大,感覺對 react 認識非常膚淺,所以在這里重新梳理一下,想想之前沒有仔細思考過的東西。
另外有說的不對的地方還請幫我指正一下,先謝謝各位啦。
目錄索引:
React 有一套合理的運行機制去控制程序在指定的時刻該做什么事,當(dāng)一個生命周期鉤子被觸發(fā)后,緊接著會有下一個鉤子,直到整個生命周期結(jié)束。
生命周期代表著每個執(zhí)行階段,比如組件初始化,更新完成,馬上要卸載等等,React 會在指定的時機執(zhí)行相關(guān)的生命周期鉤子,使我們可以有機在程序運行中會插入自己的邏輯。
我們寫代碼的時候往往會有很多組件以及他們的子組件,各自調(diào)用不同的生命周期,這時就要解決誰先誰后的問題,在 react v16 之前是采用了遞歸調(diào)用的方式一個一個執(zhí)行,而在現(xiàn)在 v16 的版本中則采用了與之完全不同的處理(調(diào)度)方式,名叫 Fiber,這個東西 facebook 做了有兩年時間,實現(xiàn)非常復(fù)雜。
具體 Fiber 它是一個什么東西呢?不要著急,我們先從最基本的生命周期鉤子看起。
首先看一下 React V16.4 后的生命周期概況(圖片來源)
constructor()
- 類構(gòu)造器初始化
static getDerivedStateFromProps()
- 組件初始化時主動觸發(fā)
render()
- 遞歸生成虛擬 DOM
componentDidMount()
- 完成首次 DOM 渲染
static getDerivedStateFromProps()
- 每次 render() 之前執(zhí)行
shouldComponentUpdate()
- 校驗是否需要執(zhí)行更新操作
render()
- 遞歸生成虛擬 DOM
getSnapshotBeforeUpdate()
- 在渲染真實 DOM 之前
componentDidUpdate()
- 完成 DOM 渲染
componentWillUnmount()
- 組件銷毀之前被直接調(diào)用
static getDerivedStateFromProps()
這個鉤子會在每個更新操作之前(即使props沒有改變)執(zhí)行一次,使用時應(yīng)該保持謹慎。
componentDidMount()
和 componentDidUpdate()
執(zhí)行的時機是差不多的,都在 render
之后,只不過前者只在首次渲染后執(zhí)行,后者首次渲染不會執(zhí)行
getSnapshotBeforeUpdate()
執(zhí)行時可以獲得只讀的新 DOM 樹,此函數(shù)的返回值為 componentDidUpdate(prevProps, prevState, snapshot)
的第三個參數(shù)
關(guān)于 Fiber,強烈建議聽一下知乎上程墨Morgan的 live 《深入理解React v16 新功能》,這里潛水員的例子和圖片也是引用于此 live。
我們知道 React 是通過遞歸的方式來渲染組件的,在 V16 版本之前的版本里,當(dāng)一個狀態(tài)發(fā)生變更時,react 會從當(dāng)前組件開始,依次遞歸調(diào)用所有的子組件生命周期鉤子,而且這個過程是同步執(zhí)行的且無法中斷的,一旦有很深很深的組件嵌套,就會造成嚴重的頁面卡頓,影響用戶體驗。
React 在V16版本之前的版本里引入了 Fiber 這樣一個東西,它的英文涵義為纖維,在計算機領(lǐng)域它排在在進程和線程的后面,雖然 React 的 Fiber 和計算機調(diào)度里的概念不一樣,但是可以方便對比理解,我們大概可以想象到 Fiber 可能是一個比線程還短的時間片段。
Fiber 把當(dāng)前需要執(zhí)行的任務(wù)分成一個個微任務(wù),安排優(yōu)先級,然后依次處理,每過一段時間(非常短,毫秒級)就會暫停當(dāng)前的任務(wù),查看有沒有優(yōu)先級較高的任務(wù),然后暫停(也可能會完全放棄)掉之前的執(zhí)行結(jié)果,跳出到下一個微任務(wù)。同時 Fiber 還做了一些優(yōu)化,可以保持住之前運行的結(jié)果以到達復(fù)用目的。
我們可以把調(diào)度當(dāng)成一個潛水員在海底尋寶,v16 之前是通過組件遞歸的方式進行尋寶,從父組件開始一層一層深入到最里面的子組件,也就是如下圖所示。
而替換成了 Fiber 后,海底變成的狹縫(簡單理解為遞歸變成了遍歷),潛水員會每隔一小段時間浮出水面,看看有沒有其他尋寶任務(wù)。注意此時沒有尋到寶藏的話,那么之前潛水的時間就浪費了。就這樣潛水員會一直下潛和冒泡,具體如下圖所示。
從生命周期那張圖片縱向來看,F(xiàn)iber 將整個生命周期分成了三個階段:
componentWillMount()
,componentWillUpdate()
,componentWillReceiveProps()
的三個生命周期鉤子被加上了 UNSAFE
標(biāo)記
簡而言之:以 render() 為界,之前執(zhí)行的生命周期都有可能會打斷并多次調(diào)用,之后的生命周期是不可被打斷的且只會調(diào)用一次。所以盡量把副作用的代碼放在只會執(zhí)行一次的 commit 階段。
除了上面常用的鉤子,React 還提供了如下鉤子:
static getDerivedStateFromError()
在 render 階段執(zhí)行,通過返回 state 更新組件狀態(tài)
componentDidCatch()
在 commit 階段執(zhí)行,可以放一些有副作用的代碼
理解了生命周期和三個執(zhí)行階段,就可以比較容易理解組件狀態(tài)的更新機制了。
這個方法可以讓我們更新組件的 state 狀態(tài)。第一個參數(shù)可以是對象,也可以是 updater 函數(shù),如果是函數(shù),則會接受當(dāng)前的 state 和 props 作為參數(shù)。第二個參數(shù)為函數(shù),是在 commit 階段后執(zhí)行,準(zhǔn)確的說是在 componentDidUpdate()
后執(zhí)行。
setState() 的更新過程是異步的(除非綁定在 DOM 事件中或?qū)懺?setTimeout 里),而且會在最后合并所有的更新,如下:
Object.assign( previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
復(fù)制代碼
之所以設(shè)計成這樣,是為了避免在一次生命周期中出現(xiàn)多次的重渲染,影響頁面性能。
如果我們想強制刷新一個組件,可以直接調(diào)用該方法,調(diào)用時會直接執(zhí)行 render()
這個函數(shù)而跳過 shouldComponentUpdate()
。
function wait() { return new Promise(resolve => {
setTimeout(() => {
resolve(); console.log("wait");
}, 0);
});
} //......省略組件創(chuàng)建 async componentDidMount() { await wait(); this.setState({ name: "new name" }); console.log("componentDidMount");
}
componentDidUpdate() { console.log("componentDidUpdate");
}
render() { console.log(this.state); return null } //......省略組件創(chuàng)建 // 輸出結(jié)果如下 // wait // {name: "new name"} // componentDidUpdate // componentDidMount // 注意 componentDidUpdate 的輸出位置,一般情況下 // componentDidUpdate 都是在componentDidMount 后面 // 執(zhí)行的,但是這里因為setState 寫在了 await 后面 // 所以情況相反。 復(fù)制代碼
了解 react 生命周期和更新機制確實有利于編寫代碼,特別是當(dāng)代碼量越來越大時,錯用的 setState 或生命周期鉤子都可能埋下越來越多的雷,直到有一天無法維護。。。
我的個人建議如下:
getDerivedStateFromProps()
當(dāng)成是 UNSAFE_componentWillReceiveProps()
的替代品,因為 getDerivedStateFromProps()
會在每次 render() 之前執(zhí)行,即使 props 沒有改變
藍藍設(shè)計( www.teruid.com )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標(biāo)定制 、 用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)。
藍藍設(shè)計的小編 http://www.teruid.com