大家好,我卡頌。
相信很多關注React
進展的朋友都瞭解Concurrent Mode
,他是漸進升級策略的產物。
由於策略調整,根據What happened to concurrent mode?,在v18
中將不會有Concurrent Mode
了。
沒有Concurrent Mode
,那該如何使用併發更新
呢?
一句話總結:在v18中,不再有三種模式,而是以是否使用併發特性作為是否開啟併發更新的依據。
更詳細的解釋,讓我們一起從React
漸進升級策略的演進過程中尋找答案。
歡迎加入人類高質量前端框架群,帶飛
React有多少種架構?
從最老的版本到當前的v18,市面上有多少個版本的React
?
可以從架構角度來概括下,當前一共有兩種架構:
- 採用不可中斷的遞迴方式更新的
Stack Reconciler
(老架構) - 採用可中斷的遍歷方式更新的
Fiber Reconciler
(新架構)
新架構可以選擇是否開啟併發更新
,所以當前市面上所有React
版本一定屬於如下一種情況:
- 老架構(v15及之前版本)
- 新架構,未開啟併發更新,與情況1行為一致(v16、v17預設屬於這種情況)
- 新架構,未開啟併發更新,但是啟用了一些新功能(比如
Automatic Batching
) - 新架構,開啟併發更新
理想與現實的差距
React
團隊的願景是:
使用老版本的開發者可以逐步升級到新版,即從情況1、2、3向情況4升級。
但是這中間存在極大的阻力,因為情況4的React
一些行為異於情況1、2、3。
比如如下三個生命週期函式在情況4的React下是“不安全的”:
componentWillMount
componentWillReceiveProps
componentWillUpdate
貿然升級可能造成老程式碼不相容。
為了讓廣大開發者能夠平滑過渡,React
團隊採用了漸進升級方案。
漸進升級第一步
漸進升級方案的第一步是規範程式碼。
v16.3新增了StrictMode
,對開發者編寫的不符合併發更新規範的程式碼作出提示,逐步引導開發者寫出規範程式碼。
比如,使用上述不安全的生命週期函式時會產生如下報錯資訊:
漸進升級第二步
下一步,React
團隊讓不同情況的React
可以在同一個頁面共存,藉此可以讓情況4的React
逐步滲入原有的專案。
具體做法是提供三種開發模式:
Legacy
模式,通過ReactDOM.render(<App />, rootNode)
建立的應用遵循該模式。預設關閉StrictMode
,表現同情況2Blocking
模式,通過ReactDOM.createBlockingRoot(rootNode).render(<App />)
建立的應用遵循該模式,作為從Legacy
向Concurrent
過渡的中間模式,預設開啟StrictMode
,表現同情況3Concurrent
模式,通過ReactDOM.createRoot(rootNode).render(<App />)
建立的應用遵循該模式,預設開啟StrictMode
,表現同情況4
為了讓不同模式的應用可以在同一個頁面內工作,需要調整一些底層實現。
比如:調整之前,大多數事件會統一冒泡到HTML元素
,調整後事件會冒泡到應用所在根元素
。
這些調整工作發生在v17,所以v17也被稱作為開啟併發更新做鋪墊的墊腳石版本。
最新的漸進升級策略
時間前進到2021年6月8日,v18工作組成立。
在與社群進行大量溝通後,React
團隊意識到當前的漸進升級策略存在兩方面問題。
原因一
首先,由於模式影響的是整個應用,所以無法在同一個應用中完成漸進升級。
舉個例子,開發者將應用中ReactDOM.render
改為ReactDOM.createBlockingRoot
,從Legacy
模式切換到Blocking
模式,這會自動開啟StrictMode
。
此時,整個應用的併發不相容警告都會上報,開發者還是需要修改整個應用。
從這個角度看,並沒有起到漸進升級的目的。
原因二
其次,React
團隊發現:開發者從新架構中獲益,更多是由於使用了併發特性
(Concurrent Feature
)。
併發特性
指開啟併發更新
後才能使用的特性,比如:
useDeferredValue
useTransition
所以,可以預設情況下仍使用同步更新
,在使用了併發特性
後再開啟併發更新
。
在v18中執行如下程式碼:
const App = () => {
const [count, updateCount] = useState(0);
const [isPending, startTransition] = useTransition();
const onClick = () => {
// 使用了併發特性useTransition
startTransition(() => {
// 本次更新是併發更新
updateCount((count) => count + 1);
});
};
return <h3 onClick={onClick}>{count}</h3>;
};
由於updateCount
在startTransition
的回撥函式中執行(使用了併發特性
),所以updateCount
會觸發併發更新
。
如果updateCount
沒有作為startTransition
的回撥函式執行,那麼updateCount
將觸發預設的同步更新
。
你可以觀察這兩種情況是否開啟時間切片
來區分是否是併發更新,完整程式碼見Demo地址
結論
在v18中,不再有三種模式,而是以是否使用併發特性作為是否開啟併發更新的依據。
具體來說,在v18中統一使用ReactDOM.createRoot
建立應用。
當不使用併發特性
時,表現如情況3。使用併發特性
後,表現如情況4。
React18
穩定版最快明年一月底到來,你還學的動嗎?