如何升級到 React 18

2022 年 3 月 8 日,作者:Rick Hanlon


如同我們在發布文章中所分享的,React 18 引入了由我們新的並行渲染器提供支援的功能,並為現有應用程式提供了漸進式的採用策略。在這篇文章中,我們將引導您完成升級到 React 18 的步驟。

回報您在升級到 React 18 時遇到的任何問題

注意

對於 React Native 使用者,React 18 將在未來的 React Native 版本中發布。這是因為 React 18 仰賴新的 React Native 架構才能受益於本部落格文章中提出的新功能。如需更多資訊,請參閱此處的 React Conf 主題演講


安裝

要安裝最新版本的 React

npm install react react-dom

或者,如果您使用的是 yarn

yarn add react react-dom

用戶端渲染 API 的更新

當您第一次安裝 React 18 時,您會在主控台看到一則警告

主控台
React 18 不再支援 ReactDOM.render。請改用 createRoot。在您切換到新的 API 之前,您的應用程式會像執行 React 17 一樣。了解更多:https://react.dev.org.tw/link/switch-to-createroot

React 18 引入了一個新的根 API,它提供了更好的根管理人體工學。新的根 API 也啟用了新的並行渲染器,讓您可以選擇使用並行功能。

// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);

// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<App tab="home" />);

我們也將 unmountComponentAtNode 改為 root.unmount

// Before
unmountComponentAtNode(container);

// After
root.unmount();

我們也從 render 中移除了回呼,因為在使用 Suspense 時,它通常不會產生預期的結果

// Before
const container = document.getElementById('app');
render(<App tab="home" />, container, () => {
console.log('rendered');
});

// After
function AppWithCallbackAfterRender() {
useEffect(() => {
console.log('rendered');
});

return <App tab="home" />
}

const container = document.getElementById('app');
const root = createRoot(container);
root.render(<AppWithCallbackAfterRender />);

注意

舊的 render 回呼 API 沒有一個一對一的替代方案 — 這取決於您的使用案例。如需更多資訊,請參閱工作小組文章用 createRoot 取代 render

最後,如果您的應用程式使用伺服器端渲染搭配 hydration,請將 hydrate 升級到 hydrateRoot

// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);

// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here.

如需更多資訊,請參閱此處的工作小組討論

注意

如果您的應用程式在升級後無法正常運作,請檢查它是否包裝在 <StrictMode> 中。 嚴格模式在 React 18 中變得更嚴格,並非所有元件都能夠承受它在開發模式中新增的檢查。如果移除嚴格模式可以修復您的應用程式,您可以在升級過程中將其移除,然後在您修復它所指出的問題後再將其新增回來(在頂層或樹狀結構的某一部分)。

伺服器渲染 API 的更新

在此版本中,我們正在改進我們的 react-dom/server API,以完全支援伺服器上的 Suspense 和串流 SSR。作為這些變更的一部分,我們將淘汰舊的 Node 串流 API,它不支援伺服器上的增量 Suspense 串流。

使用此 API 現在會發出警告

  • renderToNodeStream已淘汰 ⛔️️

相反地,對於 Node 環境中的串流,請使用

  • renderToPipeableStream新增 ✨

我們也推出了一個新的 API,以支援在現代邊緣執行時環境(例如 Deno 和 Cloudflare workers)中使用 Suspense 進行串流 SSR

  • renderToReadableStream新增 ✨

以下 API 將繼續運作,但對 Suspense 的支援有限

  • renderToString有限 ⚠️
  • renderToStaticMarkup有限 ⚠️

最後,此 API 將繼續用於渲染電子郵件

  • renderToStaticNodeStream

如需更多關於伺服器渲染 API 變更的資訊,請參閱工作小組在 伺服器端升級至 React 18 的文章、一篇關於 全新 Suspense SSR 架構的深入探討,以及 Shaundai Person 在 React Conf 2021 上關於 使用 Suspense 串流伺服器渲染 的演講。

TypeScript 定義更新

如果您的專案使用 TypeScript,您需要將 @types/react@types/react-dom 相依性更新至最新版本。新的類型更加安全,可以捕捉到以前類型檢查器忽略的問題。最顯著的變化是,現在定義 props 時需要明確列出 children prop,例如

interface MyButtonProps {
color: string;
children?: React.ReactNode;
}

請參閱 React 18 類型定義的拉取請求,以取得完整的僅限類型的變更列表。它連結到程式庫類型中的範例修正,以便您可以了解如何調整程式碼。您可以使用 自動遷移腳本,更快地將您的應用程式程式碼移植到新的、更安全的類型定義。

如果您在類型定義中發現錯誤,請在 DefinitelyTyped 儲存庫中 提交 issue

自動批次處理

React 18 透過預設執行更多批次處理來增加開箱即用的效能改進。批次處理是指 React 將多個狀態更新組合成單一重新渲染,以獲得更好的效能。在 React 18 之前,我們只在 React 事件處理程式內批次處理更新。在 promises、setTimeout、原生事件處理程式或任何其他事件內的更新,在 React 中預設不會進行批次處理。

// Before React 18 only React events were batched

function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}

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

從 React 18 的 createRoot 開始,所有更新都將自動批次處理,無論它們來自何處。這表示在逾時、promises、原生事件處理程式或任何其他事件內的更新,將與 React 事件內的更新以相同的方式進行批次處理。

// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.

function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}

setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);

這是一個重大變更,但我們預計這將導致更少的渲染工作,因此您的應用程式將擁有更好的效能。要停用自動批次處理,您可以使用 flushSync

import { flushSync } from 'react-dom';

function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}

如需更多資訊,請參閱 自動批次處理深入探討

適用於程式庫的新 API

在 React 18 工作小組中,我們與程式庫維護者合作,建立了支援並行渲染所需的新 API,以滿足其在樣式和外部儲存等領域的特定使用案例。為了支援 React 18,某些程式庫可能需要改用以下 API 之一:

  • useSyncExternalStore 是一個新的 Hook,允許外部儲存透過強制儲存更新同步來支援並行讀取。建議任何與 React 外部狀態整合的程式庫使用這個新的 API。如需更多資訊,請參閱 useSyncExternalStore 概觀文章useSyncExternalStore API 詳細資訊
  • useInsertionEffect 是一個新的 Hook,允許 CSS-in-JS 程式庫解決在渲染中注入樣式的效能問題。除非您已經建置了一個 CSS-in-JS 程式庫,否則我們不希望您使用它。這個 Hook 將在 DOM 突變後,但在佈局效應讀取新佈局之前執行。這解決了 React 17 及以下版本中已經存在的問題,但在 React 18 中更重要,因為 React 在並行渲染期間會讓瀏覽器有機會重新計算佈局。如需更多資訊,請參閱 程式庫升級指南,適用於 <style>

React 18 也引入了用於並行渲染的新 API,例如 startTransitionuseDeferredValueuseId,我們在 發行文章 中分享了更多相關資訊。

嚴格模式的更新

未來,我們希望新增一項功能,允許 React 在保留狀態的同時新增和移除 UI 區塊。例如,當使用者切換到其他畫面再返回時,React 應該能夠立即顯示先前的畫面。為此,React 將使用與之前相同的組件狀態卸載和重新掛載樹狀結構。

這項功能將使 React 開箱即用地提供更好的效能,但需要組件能夠承受多次掛載和卸載效應。大多數效應无需任何更改即可正常工作,但有些效應假設它們只會被掛載或卸載一次。

為了幫助發現這些問題,React 18 在嚴格模式中引入了一項新的僅限開發的檢查。每當組件第一次掛載時,這項新的檢查都會自動卸載並重新掛載每個組件,並在第二次掛載時恢復先前的狀態。

在此變更之前,React 會掛載元件並建立 effect。

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

在 React 18 的嚴格模式下,React 會在開發模式中模擬卸載和重新掛載元件。

* React mounts the component.
* Layout effects are created.
* Effect 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 effect setup code runs
* Effect setup code runs

更多資訊,請參閱工作小組的文章:將可重複使用的狀態添加到嚴格模式 以及 如何在 effect 中支援可重複使用的狀態

設定您的測試環境

當您第一次更新測試以使用 createRoot 時,您可能會在測試主控台中看到此警告

主控台
目前的測試環境未設定為支援 act(…)

要解決此問題,請在執行測試之前將 globalThis.IS_REACT_ACT_ENVIRONMENT 設定為 true

// In your test setup file
globalThis.IS_REACT_ACT_ENVIRONMENT = true;

此旗標的目的是告訴 React 它正在類似單元測試的環境中執行。如果您忘記使用 act 包裝更新,React 將會記錄有用的警告。

您也可以將旗標設定為 false 以告訴 React 不需要 act。這對於模擬完整瀏覽器環境的端到端測試很有用。

最終,我們預計測試函式庫會自動為您設定此項。例如,React Testing Library 的下一個版本已內建支援 React 18,無需任何額外設定。

工作小組提供了更多關於 act 測試 API 和相關變更的背景資訊

停止支援 Internet Explorer

在此版本中,React 將停止支援 Internet Explorer,該瀏覽器將於 2022 年 6 月 15 日停止支援。我們現在進行此變更,因為 React 18 中引入的新功能是使用現代瀏覽器功能(例如 microtasks)建構的,而這些功能無法在 IE 中充分地進行 polyfill。

如果您需要支援 Internet Explorer,建議您繼續使用 React 17。

棄用項目

  • react-dom: ReactDOM.render 已被棄用。使用它會發出警告並以 React 17 模式執行您的應用程式。
  • react-dom: ReactDOM.hydrate 已被棄用。使用它會發出警告並以 React 17 模式執行您的應用程式。
  • react-dom: ReactDOM.unmountComponentAtNode 已被棄用。
  • react-dom: ReactDOM.renderSubtreeIntoContainer 已被棄用。
  • react-dom/server: ReactDOMServer.renderToNodeStream 已被棄用。

其他重大變更

  • 一致的 useEffect 時機:如果更新是在離散使用者輸入事件(例如點擊或按鍵事件)期間觸發的,React 現在一律會同步清空 effect 函式。以前,這種行為並不總是可預測或一致的。
  • 更嚴格的水合錯誤:由於缺少或額外的文字內容導致的水合不匹配現在被視為錯誤而不是警告。React 將不再嘗試通過在客戶端插入或刪除節點來“修補”個別節點,以嘗試匹配伺服器標記,而是會還原到客戶端渲染,直到樹中最接近的 <Suspense> 邊界。這可確保水合樹保持一致,並避免水合不匹配可能導致的潛在隱私和安全漏洞。
  • Suspense 樹始終保持一致:如果元件在完全添加到樹之前暫停,React 不會將其以不完整狀態添加到樹中或觸發其 effect。相反,React 將完全丟棄新的樹,等待非同步操作完成,然後從頭開始重試渲染。React 將同時渲染重試嘗試,並且不會阻塞瀏覽器。
  • 具有 Suspense 的佈局 effect:當樹重新暫停並還原為後備方案時,React 現在將清除佈局 effect,然後在再次顯示邊界內的內容時重新建立它們。這修復了一個阻止元件庫在與 Suspense 一起使用時正確測量佈局的問題。
  • 新的 JS 環境需求:React 現在依賴於現代瀏覽器功能,包括 PromiseSymbolObject.assign。如果您支援較舊的瀏覽器和裝置(例如 Internet Explorer),這些瀏覽器和裝置本身不提供現代瀏覽器功能或具有不兼容的實作,請考慮在您打包的應用程式中包含全域 polyfill。

其他值得注意的變更

React

  • 組件現在可以渲染 undefined React 不再警告您從組件返回 undefined。這使得允許的組件返回值與組件樹中間允許的值一致。我們建議使用 linter 來防止錯誤,例如在 JSX 之前忘記 return 陳述式。
  • 在測試中,act 警告現在是選擇加入的: 如果您正在運行端到端測試,則不需要 act 警告。我們引入了一種選擇加入機制,因此您可以僅針對它們有用且有益的單元測試啟用它們。
  • 沒有關於在已卸載組件上 setState 的警告: 以前,當您在已卸載組件上調用 setState 時,React 會警告內存洩漏。此警告是針對訂閱添加的,但人們主要在設置狀態良好的情況下遇到它,並且解決方法會使代碼變得更糟。我們已移除此警告。
  • 不禁止控制台日誌: 當您使用嚴格模式時,React 會渲染每個組件兩次以幫助您找到意外的副作用。在 React 17 中,我們禁止了其中一次渲染的控制台日誌,以使日誌更易於閱讀。為了回應社群的回饋,指出這令人困惑,我們刪除了禁止。相反,如果您安裝了 React DevTools,則第二次日誌的渲染將顯示為灰色,並且將有一個選項(默認關閉)可以完全禁止它們。
  • 改進的內存使用情況: React 現在在卸載時清理更多內部字段,從而降低應用程序代碼中可能存在的未修復內存洩漏的嚴重程度。

React DOM 伺服器

  • renderToString 在伺服器上暫停時將不再出錯。相反,它會為最近的 <Suspense> 邊界發出後備 HTML,然後在客戶端重試渲染相同的內容。仍然建議您改用串流 API,例如 renderToPipeableStreamrenderToReadableStream
  • renderToStaticMarkup 在伺服器上暫停時將不再出錯。相反,它會發出最近的 <Suspense> 邊界的後備 HTML。

更新日誌

您可以在這裡查看完整的更新日誌。

...