一篇教你如何升級到 React 18
這是 React 官方 2022.03.08 發(fā)表的文章《How to Upgrade to the React 18 Release Candidate[1]》的譯文,通過本文,可以對 React 18 的新特性有一個全面的認知。
接下來,我還會翻譯其它幾篇比較重要的 React 18 文章,以便以更好的姿勢使用 React 18,關(guān)注不迷路。
今天,我們發(fā)布了 React 18 RC 版本。正如我們在 React Conf[2] 上分享的那樣,React 18 基于 concurrent 模式,帶來了更多能力,同時提供了漸進升級的方法。在這篇文章中,我們會一步一步的帶您升級到 React 18。
安裝
使用 @rc標簽來安裝最新版 React。
## npm
$ npm install react@rc react-dom@rc
## yarn
$ yarn add react@rc react-dom@rc
客戶端渲染 API 更新
當你首次安裝 React 18 的時候,你會看到如下警告:
- ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it’s running React 17. Learn more: https://reactjs.org/link/switch-to-createroot[3]。
React 18 提供了更合理的初始化 API,使用該 API,會自動啟用 concurrent 模式:
// 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);
root.render(<App tab="home" />);
同時我們將卸載方法從 unmountComponentAtNode 修改為 root.unmount:
// Before
unmountComponentAtNode(container);
// After
root.unmount();
我們移除了 ReactDOM.render 函數(shù)的 callback,因為當使用 Susponse 的時候,它會有問題:
// Before
const container = document.getElementById('app');
ReactDOM.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 = ReactDOM.createRoot(container);
root.render(<AppWithCallbackAfterRender />);
最后,如果你使用 hydration 來實現(xiàn)了 SSR,需要將 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.
更多信息可見 Replacing render with createRoot[4]。
SSR API 更新
在 React 18 中,為了支持服務(wù)端的 Suspense 和流式 SSR,優(yōu)化了 react-dom/server 的 API。
使用以下 API,將會拋出警告:
- renderToNodeStream:廢棄 ???。
相反,對于 Node 環(huán)境中的流式傳輸,請使用:
- renderToPipeableStream:新增 ?。
我們還引入了一個新的 API,以在現(xiàn)代邊緣運行時環(huán)境支持流式 SSR 和 Suspense,例如 Deno 和 Cloudflare workers:
- renderToReadableStream:新增 ?。
下面的兩個 API 可以繼續(xù)使用,但是不支持 Suspense:
- renderToString:限制 ??。
- renderToStaticMarkup:限制 ??。
下面的 API 沒有變化:
- renderToStaticNodeStream。
更多信息可見Upgrading to React 18 on the server[5]、New Suspense SSR Architecture in React 18[6]。
自動批處理 Automatic Batching
批處理是指:React 將多個狀態(tài)更新,聚合到一次 render 中執(zhí)行,以提升性能。
在 React 18 之前,只能在 React 自己的事件機制中使用批處理,而在 Promise、setTimeout、原生事件等場景下,是不能使用批處理的。
React 18 支持了更多場景下的批處理,以提供更好的性能。
// 在 React 18 之前,只有 React 事件,才會使用批處理
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會 re-render 一次,這就是批處理
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 會 render 兩次,每次 state 變化更新一次
}, 1000);
使用 createRoot初始化 React 18 之后,所有的狀態(tài)更新,會自動使用批處理,不關(guān)心應(yīng)用場景。
// React 18 之后,Promise、setTimeout、原生事件中,都會自動批處理
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會 re-render 一次,這就是批處理
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會 re-render 一次,這就是批處理
}, 1000);
這是一個 break change,但是我們希望這能提升你的產(chǎn)品性能。當然,你仍然可以使用 flushSync 來手動取消批處理,強制同步執(zhí)行:
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React 更新一次 DOM
flushSync(() => {
setFlag(f => !f);
});
// React 更新一次 DOM
}
更多信息可見 Automatic batching for fewer renders in React 18[7]。
三方庫 API
在 React 18 中,我們和三方庫作者合作,定義了一些新的 API,以滿足三方庫在 concurrent 模式下特定場景的訴求。比如 styles 管理、外部狀態(tài)管理、可訪問性(accessibility)等場景。
為了支持 React 18,一些三方庫可能需要用到下面的 API:
- useId 是一個新的 Hook,支持在客戶端和服務(wù)端生成唯一的 ID,同時避免 hydration 的不兼容。它可以解決在 React 17 。 及更低版本一直存在的問題。在 React 18 中,這個問題尤為重要,因為流式 SSR 返回的 HTML 片段是無序的。更多信息可見 Intent to Ship: useId[8]。
- useSyncExternalStore是一個新的 Hook,允許外部狀態(tài)管理器,強制立即同步更新,以支持并發(fā)讀取。這個新的 API 推薦用于所有 。 React 外部狀態(tài)管理庫。詳情見 useSyncExternalStore overview post[9]、useSyncExternalStore API details[10]。
- useInsertionEffect是一個新的 Hook,它可以解決 CSS-in-JS 庫在渲染中動態(tài)注入樣式的性能問題。除非你已經(jīng)構(gòu)建了一個 CSS-in-JS 庫,否則我們不希望你使用它。這個 Hook 執(zhí)行時機在 DOM 生成之后,Layout Effect 執(zhí)行之前。更多信息可見 Library Upgrade Guide for style[11]。
React 18還為 concurrent 渲染引入了新的 API,例如 startTransition 和 useDeferredValue,在即將發(fā)布的穩(wěn)定版本中會分享更多相關(guān)內(nèi)容。
嚴格模式 Strict Mode
未來,我們希望添加一個功能,允許 React 保存組件的狀態(tài),但移除 UI 部分。比如在返回舊的頁面時,React 立即恢復(fù)之前的內(nèi)容。為此,React 將使用之前保留的狀態(tài)重新加載組件。
這個功能會給 React 項目帶來非常好的體驗,但要求組件支持 state 不變的情況下,組件多次卸載和重載。
為了檢查出不合適的組件寫法,React 18 在開發(fā)模式渲染組件時,會自動執(zhí)行一次卸載,再重新加載的行為,以便檢查組件是否支持 state 不變,組件卸載重載的場景。
在以前,React 加載組件的邏輯為:
* React mounts the component.
* Layout effects are created.
* Effect effects are created.
在 React 18 嚴格模式的開發(fā)環(huán)境,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
更多信息可見:Adding Strict Effects to Strict Mode[12] 、How to Support Strict Effects[13]。
配置測試環(huán)境
當你第一次在測試用例中使用 createRoot時候,你會看到以下警告:
- The current testing environment is not configured to support act(…)。
為了修復(fù)這個問題,你需要在執(zhí)行用例之前設(shè)置 globalThis.IS_REACT_ACT_ENVIRONMENT為 true。
// In your test setup file
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
這個標記告訴 React,它在一個類似單元測試的環(huán)境中運行。如果你忘了使用 act,React 將打印一些有用的警告。你也可以將標志設(shè)置為 false 來告訴 React 不需要 act。這對于模擬瀏覽器環(huán)境的端到端測試很有用。當然,我們希望測試庫會自動為您加上這個配置。例如,下一個版本的 React Testing Library 內(nèi)置了對 React 18 的支持,無需任何額外配置。
更多信息可見:More background on the the act testing API and related changes[14]。
移除了 IE 支持
在此版本中,React 將放棄對 Internet Explorer 的支持。我們進行此更改是因為 React 18 中引入的新功能是基于現(xiàn)代瀏覽器開發(fā)的,部分能力在 IE 上是不支持的,比如 microtasks。
如果您需要支持 Internet Explorer,我們建議您繼續(xù)使用 React 17。
其它變更
- 移除“setState on unmounted component” 警告[15]。
- Susponse 不再強制需要 fallback 屬性[16]。
- 組件支持 render undefined[17]。
- 廢棄 renderSubtreeIntoContainer[18]。
- 嚴格模式不再忽略自動二次渲染組件的 log[19]。
參考資料
[1]How to Upgrade to the React 18 Release Candidate: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html。
[2]React Conf: https://reactjs.org/blog/2021/12/17/react-conf-2021-recap.html。
[3]https://reactjs.org/link/switch-to-createroot: https://reactjs.org/link/switch-to-createroot。
[4]Replacing render with createRoot: https://github.com/reactwg/react-18/discussions/5。
[5]Upgrading to React 18 on the server: https://github.com/reactwg/react-18/discussions/22。
[6]New Suspense SSR Architecture in React 18: https://github.com/reactwg/react-18/discussions/37。
[7]Automatic batching for fewer renders in React 18: https://github.com/reactwg/react-18/discussions/21。
[8]Intent to Ship: useId: https://github.com/reactwg/react-18/discussions/111。
[9]useSyncExternalStore overview post: https://github.com/reactwg/react-18/discussions/70。
[10]useSyncExternalStore API details: https://github.com/reactwg/react-18/discussions/86。
[11]Library Upgrade Guide for style: https://github.com/reactwg/react-18/discussions/110。
[12]Adding Strict Effects to Strict Mode: https://github.com/reactwg/react-18/discussions/19。
[13]How to Support Strict Effects: https://github.com/reactwg/react-18/discussions/18。
[14]More background on the the act testing API and related changes: https://github.com/reactwg/react-18/discussions/102。
[15]移除“setState on unmounted component” 警告: https://github.com/reactwg/react-18/discussions/82。
[16]Susponse 不再強制需要 fallback 屬性: https://github.com/reactwg/react-18/discussions/72。
[17]組件支持 render undefined: https://github.com/reactwg/react-18/discussions/75。
[18]廢棄 renderSubtreeIntoContainer: https://github.com/facebook/react/pull/23355。
[19]嚴格模式不再忽略自動二次渲染組件的 log: https://github.com/reactwg/react-18/discussions/96。