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 核心渲染模型的基礎更新。因此,雖然知道並行是如何運作的並不是那麼重要,但了解它在高層級是什麼可能是值得的。

Concurrent React 的一個主要特性是渲染可中斷。當你首次升級到 React 18 時,在加入任何並發功能之前,更新的渲染方式與 React 之前的版本相同,即在單一、不中斷的同步交易中。使用同步渲染時,一旦更新開始渲染,在使用者於螢幕上看到結果之前,沒有任何事物可以中斷它。

在並發渲染中,情況並非總是如此。React 可能會開始渲染更新,在中間暫停,然後稍後繼續。它甚至可能會完全放棄正在進行的渲染。React 保證即使渲染中斷,UI 仍會保持一致。為此,它會等到最後才執行 DOM 變異,也就是在評估完整個樹狀結構之後。有了這個功能,React 可以於背景中準備新的畫面,而不會阻擋主執行緒。這表示即使 UI 正在進行大型渲染工作,它也能立即回應使用者的輸入,創造出流暢的使用者體驗。

另一個範例是可重複使用的狀態。Concurrent 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

資料框架中的暫停

在 React 18 中,你可以開始在 Relay、Next.js、Hydrogen 或 Remix 等意見框架中使用 暫停 來擷取資料。使用暫停進行臨時資料擷取在技術上是可行的,但仍不建議作為一般策略。

在未來,我們可能會公開更多原始碼,讓你可以更輕鬆地使用暫停來存取資料,或許不需要使用意見框架。然而,當暫停與應用程式的架構深度整合時,它可以發揮最佳效用:你的路由器、資料層和伺服器呈現環境。因此,即使是長期而言,我們預期程式庫和框架仍會在 React 生態系統中扮演關鍵角色。

與 React 先前的版本一樣,您也可以將 Suspense 用於使用 React.lazy 在用戶端進行程式碼分割。但我們對 Suspense 的願景一直遠遠超過載入程式碼,目標是擴充 Suspense 的支援,讓最終相同的宣告式 Suspense 替代方案可以處理任何非同步作業(載入程式碼、資料、圖片等)。

伺服器元件仍在開發中

伺服器元件 是一項即將推出的功能,讓開發人員可以建置橫跨伺服器與用戶端的應用程式,結合用戶端應用程式的豐富互動性和傳統伺服器渲染的效能提升。伺服器元件與並發 React 並非內在相關,但其設計為能與 Suspense 和串流伺服器渲染等並發功能最佳搭配。

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

React 18 的新功能

新功能:自動批次處理

批次處理是 React 將多個狀態更新分組成單次重新渲染以提升效能。在沒有自動批次處理的情況下,我們只會在 React 事件處理常式內批次處理更新。預設情況下,React 沒有批次處理承諾、setTimeout、原生事件處理常式或任何其他事件內的更新。有了自動批次處理,這些更新將自動批次處理

// 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 中用於減少重新渲染的自動批次處理

新功能:過場

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

  • 緊急更新反映直接互動,例如輸入、按一下、按下等。
  • 過渡更新將 UI 從一個檢視過渡到另一個檢視。

緊急更新(例如輸入、按一下或按下)需要立即回應,以符合我們對物理物件行為的直覺。否則,它們會感覺「錯誤」。然而,過渡不同,因為使用者並不會期待在螢幕上看到每個中間值。

例如,當您在下拉式選單中選取一個篩選器時,您會期待在按一下時篩選器按鈕本身立即回應。然而,實際結果可能會另外過渡。一個小的延遲將難以察覺,而且通常是預期的。如果您在結果完成呈現之前再次變更篩選器,您只會想要看到最新的結果。

通常,為了最佳的使用者體驗,單一使用者輸入應導致緊急更新和非緊急更新。您可以在輸入事件中使用 startTransition API 來告知 React 哪些更新是緊急的,哪些是「過渡」

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 中的更新會被視為非緊急,如果出現更緊急的更新(例如按一下或按鍵),它們將會中斷。如果過渡被使用者中斷(例如,連續輸入多個字元),React 將會拋棄未完成的過時呈現工作,並只呈現最新的更新。

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

過渡將選擇加入並行呈現,這允許更新中斷。如果內容重新暫停,過渡也會告訴 React 在背景中呈現過渡內容時繼續顯示目前的內容(請參閱 Suspense RFC 以取得更多資訊)。

在此處查看轉場文件.

新的 Suspense 功能

如果元件樹的某個部分尚未準備好顯示,Suspense 讓您可以宣告式地指定載入狀態

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

Suspense 使「UI 載入狀態」成為 React 程式設計模型中的一流宣告式概念。這讓我們可以在其上建構更高級的功能。

我們在幾年前引入了 Suspense 的精簡版。但是,唯一支援的用例是使用 React.lazy 進行程式碼拆分,而且在伺服器上進行渲染時完全不支援。

在 React 18 中,我們新增了在伺服器上支援 Suspense,並使用並行渲染功能擴充了它的功能。

React 18 中的 Suspense 與轉場 API 結合使用時效果最佳。如果您在轉場期間暫停,React 會防止已顯示的內容被備用內容取代。相反地,React 會延遲渲染,直到載入足夠的資料以防止不良載入狀態。

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

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

在此版本中,我們重新設計了用戶端和伺服器上渲染時公開的 API。這些變更讓使用者在升級到 React 18 中的新 API 時,仍能繼續使用 React 17 模式中的舊 API。

React DOM Client

這些新的 API 目前已從 react-dom/client 匯出

  • createRoot:建立一個根節點來 renderunmount 的新方法。請使用它來取代 ReactDOM.render。React 18 中的新功能在沒有它的情況下無法運作。
  • hydrateRoot:用於為伺服器渲染的應用程式進行 hydrate 的新方法。請將它與新的 React DOM Server API 搭配使用,來取代 ReactDOM.hydrate。React 18 中的新功能在沒有它的情況下無法運作。

createRoothydrateRoot 都接受一個名為 onRecoverableError 的新選項,當 React 從渲染或水合期間的錯誤中復原時,你可以使用它來接收通知以進行記錄。預設情況下,React 會使用 reportError,或較舊瀏覽器中的 console.error

在此處查看 React DOM Client 文件.

React DOM Server

這些新的 API 現已從 react-dom/server 匯出,並完全支援串流伺服器上的 Suspense

  • renderToPipeableStream:用於串流 Node 環境。
  • renderToReadableStream:用於現代邊緣執行時間環境,例如 Deno 和 Cloudflare workers。

現有的 renderToString 方法仍可使用,但不建議使用。

請在此處查看 React DOM Server 的文件.

新的嚴格模式行為

未來,我們希望新增一個功能,讓 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.

請參閱文件以確保可重複使用的狀態.

新 Hooks

useId

useId 是在用戶端和伺服器上產生唯一 ID 的新 Hook,同時避免水化不匹配。它主要用於與需要唯一 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 Server

React DOM 測試工具

  • 在生產環境中使用 act 時會拋出錯誤。(#21686 by @acdlite)
  • 支援使用 global.IS_REACT_ACT_ENVIRONMENT 停用虛假的 act 警告。(#22561 by @acdlite)
  • 擴充 act 警告,以涵蓋所有可能會排程 React 工作的 API。(#22607,作者 @acdlite
  • act 批次更新。(#21797,作者 @acdlite
  • 移除未決被動效果的警告。(#22609,作者 @acdlite

React Refresh

伺服器組件(實驗性質)