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 核心渲染模型的基礎更新。因此,雖然瞭解併發的執行機制並不十分重要,但從較高的層面瞭解它是什麼可能值得。
併發 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)。
新的 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
:建立用於render
或unmount
的根節點的新方法。用它替換ReactDOM.render
。沒有它,React 18 的新特性將無法工作。hydrateRoot
:用於 hydration 伺服器端渲染應用程式的新方法。將其與新的 React DOM 伺服器 API 結合使用,以替換ReactDOM.hydrate
。沒有它,React 18 的新特性將無法工作。
createRoot
和 hydrateRoot
都接受一個名為 onRecoverableError
的新選項,以防您想在 React 從渲染或 hydration 期間的錯誤中恢復時收到通知以進行日誌記錄。預設情況下,React 將使用 reportError
,或者在較舊的瀏覽器中使用 console.error
。
React DOM 伺服器
這些新的 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.
新的 Hook
useId
useId
是一個新的 Hook,用於在客戶端和伺服器端生成唯一 ID,同時避免 hydration 錯配。它主要用於與需要唯一 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
- 新增
useTransition
和useDeferredValue
來分離緊急更新和過渡動畫。(#10426,#10715,#15593,#15272,#15578,#15769,#17058,#18796,#19121,#19703,#19719,#19724,#20672,#20976 由 @acdlite,@lunaruan,@rickhanlonii 和 @sebmarkbage 完成) - 新增
useId
用於生成唯一 ID。(#17322,#18576,#22644,#22672,#21260 由 @acdlite,@lunaruan 和 @sebmarkbage 完成) - 新增
useSyncExternalStore
來幫助外部儲存庫與 React 整合。(#15022,#18000,#18771,#22211,#22292,#22239,#22347,#23150 由 @acdlite,@bvaughn 和 @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 by @sebmarkbage) - 移除不受支援的
unstable_changedBits
API。(#20953 by @acdlite) - 允許元件渲染 undefined。(#21869 by @rickhanlonii)
- 同步重新整理由離散事件(如點選)產生的
useEffect
。(#21150 by @acdlite) - Suspense
fallback={undefined}
的行為現在與null
相同,不會被忽略。(#21854 by @rickhanlonii) - 將所有解析為相同元件的
lazy()
視為等效。(#20357 by @sebmarkbage) - 在第一次渲染期間不要修補控制檯。(#22308 by @lunaruan)
- 改進記憶體使用。(#21039 by @bgirard)
- 如果字串強制轉換丟擲異常(Temporal.*、Symbol 等),則改進訊息。(#22064 by @justingrant)
- 儘可能使用
setImmediate
代替MessageChannel
。(#20834 by @gaearon) - 修復上下文無法在掛起樹中傳播的問題。(#23095 by @gaearon)
- 透過移除急切跳出機制,修復
useReducer
觀察不正確的 props 的問題。(#22445 by @josephsavona) - 修復在 Safari 中追加 iframe 時
setState
被忽略的問題。(#23111 by @gaearon) - 修復在樹中渲染
ZonedDateTime
時發生的崩潰。(#20617 by @dimaqq) - 修復在測試中將 document 設定為
null
時發生的崩潰。(#22695 by @SimenB) - 修復啟用併發特性時
onLoad
不會觸發的問題。(#23316 by @gnoff) - 修復選擇器返回
NaN
時出現的警告。(#23333 by @hachibeeDI) - 修復在測試中將 document 設定為
null
時發生的崩潰。(#22695 by @SimenB) - 修復生成的許可證頭。(#23004 by @vitaliemiron)
- 新增
package.json
作為其中一個入口點。(#22954 by @Jack) - 允許在 Suspense 邊界之外掛起。(#23267 by @acdlite)
- 每當水合失敗時,記錄可恢復的錯誤。(#23319 by @acdlite)
React DOM
- 新增
createRoot
和hydrateRoot
。(#10239,#11225,#12117,#13732,#15502,#15532,#17035,#17165,#20669,#20748,#20888,#21072,#21417,#21652,#21687,#23207,#23385 由 @acdlite,@bvaughn,@gaearon,@lunaruan,@rickhanlonii,@trueadm 和 @sebmarkbage 完成) - 新增選擇性水合。(#14717,#14884,#16725,#16880,#17004,#22416,#22629,#22448,#22856,#23176 由 @acdlite,@gaearon,@salazarm 和 @sebmarkbage 完成)
- 將
aria-description
新增到已知 ARIA 屬性列表中。(#22142 由 @mahyareb 完成) - 將
onResize
事件新增到影片元素。(#21973 由 @rileyjshaw 完成) - 將
imageSizes
和imageSrcSet
新增到已知屬性中。(#22550 由 @eps1lon 完成) - 如果提供了
value
,則允許非字串<option>
子節點。(#21431 由 @sebmarkbage 完成) - 修復未應用
aspectRatio
樣式的問題。(#21100 由 @gaearon 完成) - 如果呼叫
renderSubtreeIntoContainer
,則發出警告。(#23355 由 @acdlite 完成)
React DOM 伺服器
- 新增新的流式渲染器。(#14144, #20970, #21056, #21255, #21200, #21257, #21276, #22443, #22450, #23247, #24025, #24030 由 @sebmarkbage 完成)
- 修復處理多個請求時SSR中的上下文提供程式。#23171 由 @frandiox 完成)
- 文字不匹配時恢復客戶端渲染。#23354 由 @acdlite 完成)
- 棄用
renderToNodeStream
。#23359 由 @sebmarkbage 完成) - 修復新的伺服器渲染器中的虛假錯誤日誌。#24043 由 @eps1lon 完成)
- 修復新的伺服器渲染器中的一個錯誤。#22617 由 @shuding 完成)
- 忽略伺服器上自定義元素內的函式和符號值。#21157 由 @sebmarkbage 完成)
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提交)