React v18.0

2022年3月29日 作者:React團隊


React 18 現已在 npm 上可用!在我們的上一篇文章中,我們分享了將您的應用程序升級到 React 18 的分步說明。在這篇文章中,我們將概述 React 18 的新增功能及其對未來的意義。


我們最新的主要版本包含開箱即用的改進,例如自動批次處理、`startTransition` 等新 API 以及支援 Suspense 的流式伺服器端渲染。

React 18 中的許多功能都構建在我們新的併發渲染器之上,這是一個幕後更改,它解鎖了強大的新功能。併發 React 是可選的——只有在您使用併發功能時才會啟用它——但我們認為它會對人們構建應用程式的方式產生重大影響。

我們花費數年時間研究和開發對 React 中併發的支援,並且我們格外小心地為現有使用者提供漸進式採用路徑。去年夏天,我們成立了 React 18 工作組,以收集社群專家的反饋,並確保整個 React 生態系統的平滑升級體驗。

如果您錯過了,我們在 React Conf 2021 上分享了這一願景的許多內容。

以下是此版本中可以預期內容的完整概述,首先是併發渲染。

注意

對於 React Native 使用者,React 18 將與新的 React Native 架構一起在 React Native 中釋出。有關更多資訊,請參閱此處 的 React Conf 主題演講

什麼是併發 React?

React 18 中最重要的補充是我們希望您永遠不必考慮的內容:併發性。我們認為這對於應用程式開發人員來說很大程度上是正確的,儘管對於庫維護人員來說,情況可能稍微複雜一些。

併發本身並不是一個功能。這是一種新的幕後機制,它使 React 能夠同時準備 UI 的多個版本。您可以將併發視為一個實現細節——它之所以有價值,是因為它解鎖的功能。React 在其內部實現中使用了複雜的技巧,例如優先順序佇列和多緩衝。但是您不會在我們的公共 API 中看到這些概念。

在設計 API 時,我們嘗試向開發人員隱藏實現細節。作為 React 開發人員,您專注於您希望使用者體驗是什麼樣子,而 React 負責如何提供該體驗。因此,我們不希望 React 開發人員知道併發在幕後是如何工作的。

但是,併發 React 比典型的實現細節更重要——它是 React 核心渲染模型的基礎更新。因此,雖然瞭解併發的執行機制並不十分重要,但從較高的層面瞭解它是什麼可能值得。

併發 React 的一個關鍵特性是渲染是可中斷的。當您第一次升級到 React 18 時,在新增任何併發功能之前,更新的渲染方式與之前的 React 版本相同——在一個單一的、不間斷的、同步的事務中。使用同步渲染,一旦更新開始渲染,在使用者能夠在螢幕上看到結果之前,沒有任何東西可以中斷它。

在併發渲染中,情況並非總是如此。React 可能會開始渲染更新,在中間暫停,然後稍後繼續。它甚至可能完全放棄正在進行的渲染。React 保證即使渲染中斷,UI 也會保持一致。為此,它會等到整個樹都已評估完畢後再執行 DOM 更改。憑藉此功能,React 可以在後臺準備新螢幕,而不會阻塞主執行緒。這意味著即使 UI 處於大型渲染任務的中間,UI 也可以立即響應使用者輸入,從而建立流暢的使用者體驗。

另一個例子是可重用的狀態。併發 React 可以從螢幕中移除 UI 的部分,然後在重用先前狀態的同時將其添加回來。例如,當用戶從螢幕切換到另一個螢幕然後再返回時,React 應該能夠以其之前狀態恢復之前的螢幕。在我們即將釋出的次要版本中,我們計劃新增一個名為<Offscreen>的新元件來實現此模式。同樣,您可以使用 Offscreen 在後臺準備新的 UI,以便在使用者顯示它之前準備好。

併發渲染是 React 中一個強大的新工具,我們的大多數新功能都是為了利用它而構建的,包括 Suspense、轉換和流式伺服器端渲染。但 React 18 只是我們計劃在此新基礎上構建的開始。

逐漸採用併發功能

從技術上講,併發渲染是一個重大更改。因為併發渲染是可中斷的,所以啟用它時,元件的行為略有不同。

在我們的測試中,我們已將數千個元件升級到 React 18。我們發現幾乎所有現有元件都“可以正常工作”併發渲染,無需任何更改。但是,其中一些可能需要一些額外的遷移工作。雖然更改通常很小,但您仍然可以按照自己的節奏進行更改。React 18 中的新渲染行為僅在使用新功能的應用程式部分啟用。

整體升級策略是在不破壞現有程式碼的情況下,讓您的應用程式執行在 React 18 上。然後,您可以根據自己的節奏逐步新增併發特性。您可以使用<StrictMode>來幫助在開發過程中發現與併發相關的錯誤。嚴格模式不會影響生產環境的行為,但在開發過程中,它會記錄額外的警告並雙重呼叫預期為冪等的函式。它不會捕獲所有錯誤,但它能有效地防止最常見的錯誤型別。

升級到 React 18 後,您可以立即開始使用併發特性。例如,您可以使用 startTransition 在螢幕之間導航而不會阻塞使用者輸入。或者使用 useDeferredValue 來限制昂貴的重新渲染。

但是,從長遠來看,我們預計您嚮應用程式新增併發的主要方式是使用支援併發的庫或框架。在大多數情況下,您無需直接與併發 API 互動。例如,路由庫會自動將導航包裝在 startTransition 中,而不是開發人員在每次導航到新螢幕時都呼叫 startTransition。

庫升級到支援併發可能需要一些時間。我們提供了新的 API,以便更容易讓庫利用併發特性。在此期間,請耐心等待維護人員,因為我們正在努力逐步遷移 React 生態系統。

更多資訊,請參閱我們之前的文章:如何升級到 React 18

資料框架中的 Suspense

在 React 18 中,您可以開始在 Relay、Next.js、Hydrogen 或 Remix 等有特定觀點的框架中使用Suspense進行資料獲取。使用 Suspense 進行臨時資料獲取在技術上是可行的,但仍然不建議作為通用策略。

將來,我們可能會公開其他基元,這可以更容易地使用 Suspense 訪問您的資料,也許無需使用特定觀點的框架。但是,Suspense 在與應用程式的架構深度整合時效果最佳:您的路由器、您的資料層和您的伺服器渲染環境。因此,即使從長遠來看,我們也預計庫和框架將在 React 生態系統中發揮關鍵作用。

與 React 的早期版本一樣,您也可以使用 Suspense 透過 React.lazy 進行客戶端程式碼分割。但是我們對 Suspense 的願景一直不僅僅是載入程式碼——目標是擴充套件對 Suspense 的支援,以便最終,相同的宣告式 Suspense 備用方案可以處理任何非同步操作(載入程式碼、資料、影像等)。

伺服器元件仍在開發中

伺服器元件 是一項即將推出的功能,它允許開發人員構建跨越伺服器和客戶端的應用程式,將客戶端應用程式豐富的互動性與傳統伺服器渲染的改進效能相結合。伺服器元件本身並不與併發 React 耦合,但它旨在與 Suspense 和流式伺服器渲染等併發特性配合使用效果最佳。

伺服器元件仍在實驗階段,但我們預計會在 18.x 的小版本中釋出初始版本。在此期間,我們正在與 Next.js、Hydrogen 和 Remix 等框架合作,以推進該提案並使其準備好廣泛採用。

React 18 的新特性

新特性:自動批處理

批處理是指 React 將多個狀態更新分組到單個重新渲染中以提高效能。如果沒有自動批處理,我們只會在 React 事件處理程式內進行批處理更新。在 Promise、setTimeout、原生事件處理程式或任何其他事件中的更新在預設情況下不會由 React 進行批處理。使用自動批處理,這些更新將自動進行批處理。

// Before: only React events were batched.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will render twice, once for each state update (no batching)
}, 1000);

// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);

更多資訊,請參閱這篇文章瞭解React 18 中用於減少渲染次數的自動批處理

新特性:Transitions

Transition 是 React 中一個新概念,用於區分緊急更新和非緊急更新。

  • 緊急更新反映直接互動,例如打字、點選、按下等等。
  • Transition 更新將 UI 從一個檢視轉換到另一個檢視。

像打字、點選或按下這樣的緊急更新需要立即響應,以符合我們對物理物件行為的直覺。否則會感覺“不對”。但是,Transitions 不同,因為使用者不希望在螢幕上看到每個中間值。

例如,當您在下拉選單中選擇過濾器時,您希望在單擊時過濾器按鈕本身立即響應。但是,實際結果可能會單獨轉換。少量延遲將難以察覺,並且通常是預期的。如果您在結果完成渲染之前再次更改過濾器,您只關心看到最新的結果。

通常,為了獲得最佳使用者體驗,單個使用者輸入應該同時產生緊急更新和非緊急更新。您可以使用輸入事件中的 startTransition API 來告知 React 哪些更新是緊急的,哪些是“Transitions”。

import { startTransition } from 'react';

// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setSearchQuery(input);
});

包裹在 startTransition 中的更新將被視為非緊急更新,如果出現更多緊急更新(例如點選或按鍵),則會中斷。如果使用者中斷了一個 Transition(例如,連續輸入多個字元),React 將丟棄未完成的陳舊渲染工作,只渲染最新的更新。

  • useTransition:一個用於啟動 Transitions 的 Hook,包括一個用於跟蹤待處理狀態的值。
  • startTransition:一個在無法使用 Hook 時用於啟動 Transitions 的方法。

Transitions 將選擇加入併發渲染,這允許中斷更新。如果內容重新暫停,Transitions 還會告訴 React 在後臺渲染 Transition 內容的同時繼續顯示當前內容(有關更多資訊,請參閱Suspense RFC)。

此處檢視 Transitions 的文件.

新的 Suspense 特性

Suspense 允許您宣告性地指定元件樹一部分的載入狀態,如果它尚未準備好顯示。

<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>

Suspense 使“UI 載入狀態”成為 React 程式設計模型中的一流宣告式概念。這讓我們能夠在其之上構建更高階的功能。

幾年前,我們推出過一個 Suspense 的限量版。然而,當時唯一支援的用例是結合 React.lazy 進行程式碼分割,並且在伺服器端渲染時完全不支援。

在 React 18 中,我們增加了對伺服器端 Suspense 的支援,並利用併發渲染功能擴充套件了其能力。

React 18 中的 Suspense 與 transition API 結合使用效果最佳。如果在 transition 期間掛起,React 將阻止已顯示的內容被 fallback 內容替換。相反,React 將延遲渲染,直到載入足夠的資料以防止出現不良的載入狀態。

更多資訊,請參閱 React 18 中 Suspense 的 RFC:React 18 中的 Suspense

新的客戶端和伺服器端渲染 API

在這個版本中,我們藉此機會重新設計了我們在客戶端和伺服器端渲染中公開的 API。這些更改允許使用者在升級到 React 18 中的新 API 時繼續在 React 17 模式下使用舊 API。

React DOM 客戶端

這些新的 API 現在從 react-dom/client 匯出。

  • createRoot:建立用於 renderunmount 的根節點的新方法。用它替換 ReactDOM.render。沒有它,React 18 的新特性將無法工作。
  • hydrateRoot:用於 hydration 伺服器端渲染應用程式的新方法。將其與新的 React DOM 伺服器 API 結合使用,以替換 ReactDOM.hydrate。沒有它,React 18 的新特性將無法工作。

createRoothydrateRoot 都接受一個名為 onRecoverableError 的新選項,以防您想在 React 從渲染或 hydration 期間的錯誤中恢復時收到通知以進行日誌記錄。預設情況下,React 將使用 reportError,或者在較舊的瀏覽器中使用 console.error

此處檢視 React DOM 客戶端文件.

React DOM 伺服器

這些新的 API 現在從 react-dom/server 匯出,並完全支援伺服器端的流式 Suspense。

  • renderToPipeableStream:用於 Node 環境中的流式傳輸。
  • renderToReadableStream:適用於現代邊緣執行時環境,例如 Deno 和 Cloudflare Workers。

現有的 renderToString 方法仍然有效,但不推薦使用。

此處檢視 React DOM 伺服器文件.

新的嚴格模式行為

將來,我們希望新增一個功能,允許 React 在保留狀態的同時新增和刪除 UI 的部分。例如,當用戶從一個螢幕切換到另一個螢幕再返回時,React 應該能夠立即顯示之前的螢幕。為此,React 將使用與之前相同的元件狀態解除安裝和重新掛載樹。

此功能將使 React 應用程式開箱即用地獲得更好的效能,但需要元件能夠承受多次掛載和銷燬效果的影響。大多數效果無需任何更改即可工作,但某些效果假設它們只掛載或銷燬一次。

為了幫助發現這些問題,React 18 在嚴格模式中引入了一種新的僅限開發的檢查。此新檢查將在元件首次掛載時自動解除安裝和重新掛載每個元件,並在第二次掛載時恢復先前狀態。

在此更改之前,React 將掛載元件並建立效果。

* React mounts the component.
* Layout effects are created.
* Effects are created.

在 React 18 的嚴格模式下,React 將在開發模式下模擬解除安裝和重新掛載元件。

* React mounts the component.
* Layout effects are created.
* Effects are created.
* React simulates unmounting the component.
* Layout effects are destroyed.
* Effects are destroyed.
* React simulates mounting the component with the previous state.
* Layout effects are created.
* Effects are created.

此處檢視確保可重用狀態的文件.

新的 Hook

useId

useId 是一個新的 Hook,用於在客戶端和伺服器端生成唯一 ID,同時避免 hydration 錯配。它主要用於與需要唯一 ID 的輔助功能 API 整合的元件庫。這解決了 React 17 及以下版本中已存在的問題,但由於新的流式伺服器渲染器如何無序地提供 HTML,因此在 React 18 中它更加重要。此處檢視文件

注意

useId 不能用於生成 列表中的鍵。鍵應從您的資料中生成。

useTransition

useTransitionstartTransition 允許您將某些狀態更新標記為非緊急狀態。預設情況下,其他狀態更新被認為是緊急的。React 將允許緊急狀態更新(例如,更新文字輸入)中斷非緊急狀態更新(例如,渲染搜尋結果列表)。此處檢視文件

useDeferredValue

useDeferredValue 允許您延遲重新渲染樹的非緊急部分。它類似於去抖動,但與之相比有一些優勢。沒有固定的時間延遲,因此 React 將在第一次渲染反映在螢幕上後立即嘗試延遲渲染。延遲渲染是可以中斷的,並且不會阻塞使用者輸入。此處檢視文件

useSyncExternalStore

useSyncExternalStore 是一個新的 Hook,它允許外部儲存透過強制同步更新儲存來支援併發讀取。它消除了在實現對外部資料來源的訂閱時對 useEffect 的需求,並推薦用於與 React 外部狀態整合的任何庫。此處檢視文件

注意

useSyncExternalStore 旨在供庫使用,而不是應用程式程式碼。

useInsertionEffect

useInsertionEffect 是一個新的 Hook,它允許 CSS-in-JS 庫解決在渲染中注入樣式的效能問題。除非您已經構建了 CSS-in-JS 庫,否則我們預計您永遠不會使用它。此 Hook 將在 DOM 被修改後但佈局效果讀取新佈局之前執行。這解決了在 React 17 及以下版本中已存在的問題,但在 React 18 中更為重要,因為 React 在併發渲染期間會讓位於瀏覽器,從而使其有機會重新計算佈局。此處檢視文件

注意

useInsertionEffect 旨在供庫使用,而不是應用程式程式碼。

如何升級

請檢視 如何升級到 React 18,以獲取分步說明和完整的重大更改和值得注意更改列表。

變更日誌

React

React DOM

React DOM 伺服器

React DOM 測試工具

  • 在生產環境中使用act時丟擲異常。#21686@acdlite 完成)
  • 支援使用global.IS_REACT_ACT_ENVIRONMENT停用虛假的act警告。#22561@acdlite 完成)
  • 擴充套件act警告以涵蓋所有可能排程React工作的API。#22607@acdlite 完成)
  • 使act批次更新。#21797@acdlite 完成)
  • 刪除懸空被動效果的警告。#22609@acdlite 完成)

React 快速重新整理

  • 在快速重新整理中跟蹤後期掛載的根。#22740@anc95 完成)
  • package.json檔案中新增exports欄位。(#23087@otakustay提交)

伺服器元件(實驗性)

  • 新增伺服器上下文支援。(#23244@salazarm提交)
  • 新增lazy支援。(#24068@gnoff提交)
  • 更新webpack外掛以支援webpack 5 (#22739@michenly提交)
  • 修復Node載入器中的錯誤。(#22537@btea提交)
  • 對於邊緣環境,使用globalThis代替window。(#22777@huozhi提交)