React 18不再依賴Concurrent Mode開啟併發了

卡頌發表於2021-11-23

大家好,我卡頌。

相信很多關注React進展的朋友都瞭解Concurrent Mode,他是漸進升級策略的產物。

由於策略調整,根據What happened to concurrent mode?,在v18中將不會有Concurrent Mode了。

沒有Concurrent Mode,那該如何使用併發更新呢?

一句話總結:在v18中,不再有三種模式,而是以是否使用併發特性作為是否開啟併發更新的依據。

更詳細的解釋,讓我們一起從React漸進升級策略的演進過程中尋找答案。

歡迎加入人類高質量前端框架群,帶飛

React有多少種架構?

從最老的版本到當前的v18,市面上有多少個版本的React

可以從架構角度來概括下,當前一共有兩種架構:

  • 採用不可中斷的遞迴方式更新的Stack Reconciler(老架構)
  • 採用可中斷的遍歷方式更新的Fiber Reconciler(新架構)

新架構可以選擇是否開啟併發更新,所以當前市面上所有React版本一定屬於如下一種情況:

  1. 老架構(v15及之前版本)
  2. 新架構,未開啟併發更新,與情況1行為一致(v16、v17預設屬於這種情況)
  3. 新架構,未開啟併發更新,但是啟用了一些新功能(比如Automatic Batching
  4. 新架構,開啟併發更新

理想與現實的差距

React團隊的願景是:

使用老版本的開發者可以逐步升級到新版,即從情況1、2、3向情況4升級。

但是這中間存在極大的阻力,因為情況4的React一些行為異於情況1、2、3。

比如如下三個生命週期函式在情況4的React下是“不安全的”:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

貿然升級可能造成老程式碼不相容。

為了讓廣大開發者能夠平滑過渡,React團隊採用了漸進升級方案。

漸進升級第一步

漸進升級方案的第一步是規範程式碼。

v16.3新增了StrictMode,對開發者編寫的不符合併發更新規範的程式碼作出提示,逐步引導開發者寫出規範程式碼。

比如,使用上述不安全的生命週期函式時會產生如下報錯資訊:

StrictMode下使用不安全生命週期函式報錯

漸進升級第二步

下一步,React團隊讓不同情況的React可以在同一個頁面共存,藉此可以讓情況4的React逐步滲入原有的專案。

具體做法是提供三種開發模式:

  1. Legacy模式,通過ReactDOM.render(<App />, rootNode)建立的應用遵循該模式。預設關閉StrictMode,表現同情況2
  2. Blocking模式,通過ReactDOM.createBlockingRoot(rootNode).render(<App />)建立的應用遵循該模式,作為從LegacyConcurrent過渡的中間模式,預設開啟StrictMode,表現同情況3
  3. Concurrent模式,通過ReactDOM.createRoot(rootNode).render(<App />)建立的應用遵循該模式,預設開啟StrictMode,表現同情況4

3種模式可用特性對比

為了讓不同模式的應用可以在同一個頁面內工作,需要調整一些底層實現。

比如:調整之前,大多數事件會統一冒泡到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>;
};

由於updateCountstartTransition的回撥函式中執行(使用了併發特性),所以updateCount會觸發併發更新

如果updateCount沒有作為startTransition的回撥函式執行,那麼updateCount將觸發預設的同步更新

你可以觀察這兩種情況是否開啟時間切片來區分是否是併發更新,完整程式碼見Demo地址

結論

在v18中,不再有三種模式,而是以是否使用併發特性作為是否開啟併發更新的依據。

具體來說,在v18中統一使用ReactDOM.createRoot建立應用。

當不使用併發特性時,表現如情況3。使用併發特性後,表現如情況4。

React18穩定版最快明年一月底到來,你還學的動嗎?

相關文章