大家好,我卡頌。
本文我們來看React
內部Effects List
機制重構的前因後果。
閱讀完本文,你可以掌握React18
對比之前版本,Suspense
特性的差異及原因。
歡迎加入人類高質量前端框架群,帶飛
什麼是副作用
簡易的React
工作原理可以概括為:
- 觸發
更新
- render階段:計算
更新
會造成的副作用
- commit階段:執行
副作用
副作用
包含很多型別,比如:
Placement
指DOM節點的插入與移動
Passive
指useEffect
回撥執行ChildDeletion
指移除子DOM節點
- 等等
更新造成DOM
變化主要就是Placement
、ChildDeletion
在起作用。
那麼render階段
如何儲存副作用
,commit階段
又是如何使用副作用
的呢?
Effects List
在重構前,render階段
,帶有副作用
的節點會連線形成連結串列,這條連結串列被稱為Effects List
。
比如下圖,B、C、E存在副作用
,連線形成Effects List
:
commit階段
不需要從A向下遍歷整棵樹,只需要遍歷Effects List
就能找到所有有副作用
的節點並執行對應操作。
SubtreeFlags
在重構之後,會將子節點的副作用
冒泡到父節點的SubtreeFlags
屬性。
比如B、C、E包含的副作用
如下圖:
冒泡流程如下:
- B的
副作用
為Passive
,冒泡到A,A.SubtreeFlags
包含Passive
- E的
副作用
為Placement
,冒泡到D,D.SubtreeFlags
包含Placement
- D冒泡到C,
C.SubtreeFlags
包含Placement
- C的
副作用
為Update
,C.SubtreeFlags
包含Placement
,C冒泡到A - 最終
A.SubtreeFlags
包含Passive
、Placement
、Update
這就代表A的子樹中包含這三種副作用。
在commit階段
,再根據SubtreeFlags
一層層查詢有副作用
的節點並執行對應操作。
可見,SubtreeFlags
需要遍歷樹,而Effects List
只需要遍歷連結串列,效率更高。那麼React
為什麼要重構呢?
Suspense
答案是:SubtreeFlags
遍歷子樹的操作雖然比Effects List
需要遍歷更多節點,但是React18
中一種新特性恰恰需要遍歷子樹。
這個特性就是Suspense
。
Suspense
是v16
就提供的功能,但v18
之後,當開啟併發功能,Suspense
與之前版本的行為是有區別的。
考慮如下元件:
<Suspense fallback={<h3>loading...</h3>}>
<LazyCpn />
<Sibling />
</Suspense>
其中LazyCpn
是使用React.lazy
包裹的非同步載入元件
。
Sibling
程式碼如下:
function Sibling() {
useEffect(() => {
console.log("Sibling effect");
}, []);
return <h1>Sibling</h1>;
}
由於Suspense
會等待子孫元件中的非同步請求完畢後再渲染,所以當程式碼執行時頁面首先會渲染fallback
:
<h3>loading...</h3>
但是Sibling
並不是非同步的!這裡就體現了新舊版本React
的差異。
新舊版React的差異
再回顧下開篇介紹的簡易React
工作原理:
- 觸發
更新
- render階段:協調器計算
更新
會造成的副作用
- commit階段:渲染器執行
副作用
在開啟併發之前,React
保證一次render階段
對應一次commit階段
。
所以在上例中,雖然由於LazyCpn
在請求導致Suspense
渲染fallback
,但是並不會阻止Sibling
渲染,也不會阻止Sibling
中useEffect
的執行。
控制檯還是會列印Sibling effect。
同時,為了在視覺上顯得Sibling
沒有渲染,Sibling
渲染的DOM節點
會被設定display: none
:
但這其實挺hack
的。畢竟根據Suspense
的理念,如果子孫元件有非同步載入的內容,那應該只渲染fallback
(而不是同時渲染display: none
的內容)
所以在新版中,針對Suspense
內不顯示的子樹做了單獨的處理,既不會渲染display: none
的內容,也不會執行useEffect
回撥:
要實現這部分處理的基礎,就是改變commit階段
遍歷的方式,也就回到開篇提到的Effects List
重構為subtreeFlags
。
你可以從這個線上Demo直觀的感受新舊版Suspense
的差異
總結
今天我們又學到了一個React
原始碼小知識。
值得一提的是,針對Suspense
的這次改進,為React
帶來一種新的內部元件型別 —— Offscreen Component
。
未來他可能是實現React
版keep-alive
的基礎。