流程
- react在diff之后,會進入commit階段,將新生成的虛擬DOM發生的變化映射到真實DOM上
- 在commit的前期,會調度一些生命周期方法,對於類組件來說,會觸發getSnapshotBeforeUpdate。對於函數組件來說,會調度useEffect。
- 但是並不是立即執行,在此階段,會把useEffect入列到react維護的調度隊列中,給一個普通的優先級,異步執行。
- 之后,在將新生成的虛擬DOM發生的變化映射到真實DOM上的過程中,會通過commitWalk根據不同的fiberNode進行DOM修改。
- 當commitWalk遇到類組件時,不會進行任何操作,因為沒有真實的dom節點,直接return,處理下一個節點
- 但對於有了hooks的函數組件來說,會同步調用上一次渲染時useLayoutEffect函數的destroy方法
- 注意節點在commitWalk后,變化已經被映射到真實的DOM上了
- 但是由於JS線程和渲染線程是互斥的,真實的DOM雖然已經改變了,瀏覽器卻沒有立刻渲染到屏幕上
- 此時會同步執行對應的生命周期方法,如componentDidmount、componentDidUpdate以及useLayoutEffect的create方法。此時如果對DOM進行了操作,會一起處理
- 在react中,commit不會被打斷,直至所有節點都commit結束,react更新完成,JS才停止執行
- 瀏覽器此時才會進入渲染線程,將發生變化的DOM渲染到屏幕上,因此react只用了一次回流、重繪的代價,將所有需要更新的DOM節點更新了
- 瀏覽器渲染完成后,react開始之前之前提到的調度隊列,才開始執行useEffct產生的函數
一些結論
-
useEffect的create函數,調用時機和位置都與componentDidMount,componentDidUpdate 一致,且都是被 React 同步調用,都會阻塞瀏覽器渲染。
-
將對DOM的操作放在useLayoutEffect中,性能更好