如何升级到 React 18
2022 年 3 月 8 日 由 Rick Hanlon
正如我们在 发布文章 中分享的那样,React 18 引入了由我们的新并发渲染器驱动的功能,并为现有应用程序采用了一种渐进式策略。在这篇文章中,我们将指导您完成升级到 React 18 的步骤。
如果您在升级到 React 18 时遇到任何问题,请 报告。
安装
要安装最新版本的 React
npm install react react-dom
或者如果您使用的是 yarn
yarn add react react-dom
客户端渲染 API 的更新
当您首次安装 React 18 时,您将在控制台中看到一个警告
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 />);
最后,如果您的应用程序使用服务器端渲染与水合,请将 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.
有关更多信息,请参见 工作组讨论。
服务器端渲染 API 的更新
在这个版本中,我们正在改进我们的 react-dom/server
API,以完全支持服务器上的 Suspense 和流式 SSR。作为这些更改的一部分,我们正在弃用旧的 Node 流 API,它不支持在服务器上进行增量 Suspense 流式传输。
使用此 API 现在将发出警告
renderToNodeStream
: 已弃用 ⛔️️
相反,对于在 Node 环境中进行流式传输,请使用
renderToPipeableStream
: 新的 ✨
我们还引入了一个新的 API 来支持使用 Suspense 为现代边缘运行时环境(如 Deno 和 Cloudflare worker)进行流式 SSR
renderToReadableStream
: 新的 ✨
以下 API 将继续工作,但对 Suspense 的支持有限
renderToString
: 有限的 ⚠️renderToStaticMarkup
: 有限的 ⚠️
最后,此 API 将继续用于渲染电子邮件
renderToStaticNodeStream
有关服务器端渲染 API 更改的更多信息,请参见关于 在服务器上升级到 React 18 的工作组帖子,关于 新的 Suspense SSR 架构的深入分析,以及 Shaundai Person 关于 使用 Suspense 进行流式服务器端渲染 的演讲(在 React Conf 2021 上)。
TypeScript 定义的更新
如果您的项目使用 TypeScript,则需要将您的 @types/react
和 @types/react-dom
依赖项更新到最新版本。新的类型更安全,可以捕获以前由类型检查器忽略的问题。最显着的变化是 children
道具现在需要在定义道具时显式列出,例如
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}
有关类型更改的完整列表,请参阅React 18 类型声明 pull 请求。它链接到库类型中的示例修复,以便您可以了解如何调整您的代码。您可以使用自动迁移脚本帮助您更快地将应用程序代码移植到新的更安全的类型声明。
如果您在类型声明中发现错误,请在 DefinitelyTyped 仓库中提交问题。
自动批量处理
React 18 通过默认情况下进行更多批量处理,添加了开箱即用的性能改进。批量处理是指 React 将多个状态更新分组到单个重新渲染中,以提高性能。在 React 18 之前,我们只在 React 事件处理程序内部批量更新。在 promise、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
,所有更新将自动批量处理,无论它们来自哪里。这意味着在超时、promise、原生事件处理程序或任何其他事件内部的更新将与 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,它允许外部存储通过强制对存储的更新是同步的来支持并发读取。此新 API 适用于任何与 React 外部状态集成的库。有关更多信息,请参阅useSyncExternalStore 概述文章 和 useSyncExternalStore API 详细信息。useInsertionEffect
是一个新的 Hook,它允许 CSS-in-JS 库解决在渲染中注入样式的性能问题。除非您已经构建了一个 CSS-in-JS 库,否则我们预计您永远不会使用它。此 Hook 将在 DOM 被修改后运行,但在布局效果读取新布局之前运行。这解决了一个在 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为 React 在并发渲染期间会让位给浏览器,从而让浏览器有机会重新计算布局。有关更多信息,请参阅<style>
的库升级指南。
React 18 还引入了并发渲染的新 API,例如 startTransition
、useDeferredValue
和 useId
,我们在发布文章 中分享了更多关于这些 API 的信息。
严格模式的更新
将来,我们希望添加一项功能,允许 React 添加和删除 UI 的部分,同时保留状态。例如,当用户从屏幕上跳出然后返回时,React 应该能够立即显示上一个屏幕。为此,React 将使用与之前相同的组件状态来卸载和重新安装树。
此功能将为 React 提供更好的开箱即用性能,但要求组件对效果多次安装和销毁具有弹性。大多数效果无需任何更改即可正常工作,但一些效果假定它们只安装或销毁一次。
为了帮助发现这些问题,React 18 在严格模式中引入了一项新的开发专用检查。此新的检查将在组件第一次安装时自动卸载和重新安装每个组件,并在第二次安装时恢复先前的状态。
在此更改之前,React 将安装组件并创建效果。
* 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
有关更多信息,请参阅工作组帖子,了解向严格模式添加可重用状态 和 如何在效果中支持可重用状态。
配置您的测试环境
当您第一次将测试更新为使用 createRoot
时,您可能会在测试控制台中看到此警告。
要解决此问题,请在运行测试之前将 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 中引入的新功能是使用诸如微任务之类的现代浏览器功能构建的,而这些功能在 IE 中无法得到充分的填充。
如果您需要支持 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 现在始终同步刷新效果函数,如果更新是在离散用户输入事件(如点击或按键事件)期间触发的。以前,行为并不总是可预测或一致的。
- 更严格的 hydration 错误: 由于缺少或多余的文本内容导致的 hydration 匹配错误现在被视为错误,而不是警告。React 将不再尝试通过在客户端插入或删除节点来“修补”各个节点以尝试匹配服务器标记,而将还原到客户端渲染,直到树中最近的
<Suspense>
边界。这确保了 hydrated 树是一致的,并避免了 hydration 匹配错误可能导致的潜在隐私和安全漏洞。 - Suspense 树始终保持一致:如果组件在其完全添加到树之前挂起,React 不会以不完整状态将其添加到树中,也不会触发其效果。相反,React 将完全丢弃新树,等待异步操作完成,然后从头开始重试渲染。React 将并发地渲染重试尝试,并且不会阻塞浏览器。
- 具有 Suspense 的布局效果: 当树重新挂起并恢复到备用内容时,React 现在将清理布局效果,然后在边界内的内容再次显示时重新创建它们。这修复了一个问题,该问题阻止组件库在与 Suspense 一起使用时正确测量布局。
- 新的 JS 环境要求: React 现在依赖于现代浏览器功能,包括
Promise
、Symbol
和Object.assign
。如果您支持 Internet Explorer 等不支持现代浏览器功能或具有不兼容实现的旧浏览器和设备,请考虑在捆绑的应用程序中包含全局填充。
其他值得注意的更改
React
- 组件现在可以渲染
undefined
: React 现在不会再发出警告,如果您从组件中返回undefined
。这使允许的组件返回值与组件树中间允许的值一致。我们建议使用 linter 来防止类似于在 JSX 之前忘记return
语句之类的错误。 - 在测试中,
act
警告现在是可选的:如果您正在运行端到端测试,则act
警告是不必要的。我们引入了一种 可选 机制,以便您仅在它们有用且有益的单元测试中启用它们。 - 没有关于在未挂载的组件上
setState
的警告:以前,React 在您在未挂载的组件上调用setState
时会发出有关内存泄漏的警告。此警告是为订阅添加的,但人们主要在设置状态没问题的情况下遇到它,并且解决方法会使代码更糟。我们已经 删除 了此警告。 - 不抑制控制台日志:当您使用严格模式时,React 会渲染每个组件两次以帮助您查找意外的副作用。在 React 17 中,我们已经抑制了两个渲染中的一个的控制台日志,以使日志更容易阅读。为了响应 社区反馈,关于这一点令人困惑,我们已删除了抑制。相反,如果您安装了 React DevTools,第二个日志的渲染将以灰色显示,并且将有一个选项(默认情况下关闭)完全抑制它们。
- 改进的内存使用量:React 现在在卸载时清理了更多内部字段,使应用程序代码中可能存在的未修复内存泄漏的影响不那么严重。
React DOM Server
renderToString
: 在服务器上挂起时将不再出错。相反,它将为最近的<Suspense>
边界发出备用 HTML,然后在客户端上重试渲染相同的内容。仍然建议您切换到流式 API,如renderToPipeableStream
或renderToReadableStream
。renderToStaticMarkup
: 在服务器上挂起时将不再出错。相反,它将为最近的<Suspense>
边界发出备用 HTML。
更新日志
您可以查看 完整更新日志.