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 18 如何符合我們讓開發人員輕鬆建構絕佳使用者體驗的使命
- Shruti Kapoor 展示如何使用 React 18 中的新功能
- Shaundai Person 為我們概述了 使用 Suspense 進行串流伺服器渲染
以下是此版本預期的完整概述,從並發渲染開始。
什麼是並行的 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
:建立一個根節點來render
或unmount
的新方法。請使用它來取代ReactDOM.render
。React 18 中的新功能在沒有它的情況下無法運作。hydrateRoot
:用於為伺服器渲染的應用程式進行 hydrate 的新方法。請將它與新的 React DOM Server API 搭配使用,來取代ReactDOM.hydrate
。React 18 中的新功能在沒有它的情況下無法運作。
createRoot
和 hydrateRoot
都接受一個名為 onRecoverableError
的新選項,當 React 從渲染或水合期間的錯誤中復原時,你可以使用它來接收通知以進行記錄。預設情況下,React 會使用 reportError
,或較舊瀏覽器中的 console.error
。
React DOM Server
這些新的 API 現已從 react-dom/server
匯出,並完全支援串流伺服器上的 Suspense
renderToPipeableStream
:用於串流 Node 環境。renderToReadableStream
:用於現代邊緣執行時間環境,例如 Deno 和 Cloudflare workers。
現有的 renderToString
方法仍可使用,但不建議使用。
新的嚴格模式行為
未來,我們希望新增一個功能,讓 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
useTransition
和 startTransition
讓你可以標示一些狀態更新為非緊急。預設情況下,其他狀態更新會被視為緊急。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
- Add
useTransition
anduseDeferredValue
to separate urgent updates from transitions. (#10426, #10715, #15593, #15272, #15578, #15769, #17058, #18796, #19121, #19703, #19719, #19724, #20672, #20976 by @acdlite, @lunaruan, @rickhanlonii, and @sebmarkbage) - 新增
useId
以產生獨特的 ID。(#17322、#18576、#22644、#22672、#21260 由 @acdlite、@lunaruan 和 @sebmarkbage 提出) - Add
useSyncExternalStore
to help external store libraries integrate with React. (#15022, #18000, #18771, #22211, #22292, #22239, #22347, #23150 by @acdlite, @bvaughn, and @drarmstr) - 新增
startTransition
作為useTransition
的版本,不含待處理的回饋。(#19696 由 @rickhanlonii 提出) - 新增
useInsertionEffect
供 CSS-in-JS 函式庫使用。(#21913 由 @rickhanlonii 提出) - 當內容重新出現時,讓 Suspense 重新掛載佈局效果。(#19322、#19374、#19523、#20625、#21079,由 @acdlite、@bvaughn 和 @lunaruan 提出)
- 讓
<StrictMode>
重新執行效果以檢查可還原的狀態。(#19523 , #21418 由 @bvaughn 和 @lunaruan) - 假設符號總是可用。(#23348 由 @sebmarkbage)
- 移除
object-assign
polyfill。(#23351 由 @sebmarkbage) - 移除已不再支援的
unstable_changedBits
API。(#20953 由 @acdlite 提出) - 允許元件呈現未定義的狀態。(#21869 由 @rickhanlonii 提出)
- 同步執行
useEffect
,其結果來自於像是點擊等離散事件。(#21150 由 @acdlite 提出) - Suspense
fallback={undefined}
現在的行為與null
相同,且不會被忽略。(#21854 由 @rickhanlonii 提出) - 考慮所有
lazy()
解析為相同的元件等價物。(#20357 由 @sebmarkbage) - 在第一次渲染期間不要修補主控台。(#22308 由 @lunaruan)
- 改善記憶體使用量。(#21039 由 @bgirard)
- 如果字串強制轉型拋出例外(Temporal.*、Symbol 等),改善訊息(#22064 由 @justingrant 提出)
- 如果可以使用
setImmediate
,請優先使用它,而非MessageChannel
。(#20834 由 @gaearon 提出) - 修正 suspended 樹狀結構內部無法傳播 context 的問題。(#23095 由 @gaearon 提出)
- 修正
useReducer
觀察到不正確 props 的問題,方法是移除急切退出機制。(#22445 由 @josephsavona 提出) - 修復在 Safari 中追加 iframe 時忽略
setState
的問題。(#23111,作者 @gaearon) - 修復在樹狀結構中呈現
ZonedDateTime
時發生的崩潰問題。(#20617,作者 @dimaqq) - 修復在測試中將文件設定為
null
時發生的崩潰問題。(#22695,作者 @SimenB) - 修復在並行功能開啟時,
onLoad
未觸發的問題。(#23316,作者 @gnoff) - 修正選取器傳回
NaN
時的警告。(#23333 由 @hachibeeDI 提出) - 修復在測試中將文件設定為
null
時發生的崩潰問題。(#22695,作者 @SimenB) - 修正產生的授權標頭。(#23004 由 @vitaliemiron 提出)
- 新增
package.json
為其中一個進入點。(#22954 由 @Jack 提出) - 允許在 Suspense 邊界外中斷。(#23267 由 @acdlite 提出)
- 每當水化失敗時,記錄可復原的錯誤。(#23319 由 @acdlite 提出)
React DOM
- Add
createRoot
andhydrateRoot
. (#10239, #11225, #12117, #13732, #15502, #15532, #17035, #17165, #20669, #20748, #20888, #21072, #21417, #21652, #21687, #23207, #23385 by @acdlite, @bvaughn, @gaearon, @lunaruan, @rickhanlonii, @trueadm, and @sebmarkbage) - Add selective hydration. (#14717, #14884, #16725, #16880, #17004, #22416, #22629, #22448, #22856, #23176 by @acdlite, @gaearon, @salazarm, and @sebmarkbage)
- 將
aria-description
加入已知的 ARIA 屬性清單中。(#22142 由 @mahyareb 提出) - 將
onResize
事件加入影片元素中。(#21973 由 @rileyjshaw 提出) - 將
imageSizes
和imageSrcSet
加入已知的 props 中。(#22550 由 @eps1lon 提出) - 如果提供了
value
,則允許非字串<option>
子項。(#21431 由 @sebmarkbage 提出) - 修正未套用
aspectRatio
樣式。(#21100 由 @gaearon 提出) - 如果呼叫
renderSubtreeIntoContainer
,請發出警告。(#23355,@acdlite)
React DOM Server
- Add the new streaming renderer. (#14144, #20970, #21056, #21255, #21200, #21257, #21276, #22443, #22450, #23247, #24025, #24030 by @sebmarkbage)
- 處理多個要求時,修正 SSR 中的內容提供者。(#23171,@frandiox)
- 文字不符時,還原為用戶端渲染。(#23354 by @acdlite)
- 棄用
renderToNodeStream
。(#23359 by @sebmarkbage) - 修復新伺服器渲染器中的虛假錯誤記錄。(#24043 by @eps1lon)
- 修復新伺服器渲染器中的錯誤。(#22617 by @shuding)
- 忽略伺服器上自訂元素內的函數和符號值。(#21157 by @sebmarkbage)
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
- 在快速更新中追蹤延後掛載的根節點。(#22740 由 @anc95)
- 將
exports
欄位加入package.json
。(#23087 由 @otakustay)