如何升级到 React 18
2022年3月8日 由 Rick Hanlon
🌐 March 08, 2022 by Rick Hanlon
正如我们在发布文章中分享的,React 18 引入了由我们新的并发渲染器驱动的功能,并为现有应用提供了渐进式采用策略。在本文中,我们将引导你了解升级到 React 18 的步骤。
🌐 As we shared in the release post, React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18.
🌐 Please report any issues you encounter while upgrading to React 18.
正在安装
🌐 Installing
安装最新版 React:
🌐 To install the latest version of React:
npm install react react-dom或者如果你使用 yarn:
🌐 Or if you’re using yarn:
yarn add react react-dom客户端渲染 API 更新
🌐 Updates to Client Rendering APIs
当你第一次安装 React 18 时,你会在控制台看到一个警告:
🌐 When you first install React 18, you will see a warning in the console:
React 18 引入了一个新的根 API,它在管理根节点时提供了更好的易用性。新的根 API 还支持新的并发渲染器,使你可以选择使用并发功能。
🌐 React 18 introduces a new root API which provides better ergonomics for managing roots. The new root API also enables the new concurrent renderer, which allows you to opt-into concurrent features.
// 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:
🌐 We’ve also changed unmountComponentAtNode to root.unmount:
// Before
unmountComponentAtNode(container);
// After
root.unmount();我们还从 render 中移除了回调,因为在使用 Suspense 时,它通常不会产生预期的结果:
🌐 We’ve also removed the callback from render, since it usually does not have the expected result when using 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:
🌐 Finally, if your app uses server-side rendering with hydration, upgrade hydrate to 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.欲了解更多信息,请参见此处的工作组讨论。
🌐 For more information, see the working group discussion here.
服务器渲染 API 更新
🌐 Updates to Server Rendering APIs
在此版本中,我们正在重构我们的 react-dom/server API,以完全支持服务器端的 Suspense 和流式 SSR。作为这些更改的一部分,我们正在弃用旧的 Node 流式 API,该 API 不支持服务器端的增量 Suspense 流式处理。
🌐 In this release, we’re revamping our react-dom/server APIs to fully support Suspense on the server and Streaming SSR. As part of these changes, we’re deprecating the old Node streaming API, which does not support incremental Suspense streaming on the server.
使用此 API 现在会警告:
🌐 Using this API will now warn:
renderToNodeStream:已弃用 ⛔️️
相反,在 Node 环境中进行流式处理时,请使用:
🌐 Instead, for streaming in Node environments, use:
renderToPipeableStream:新 ✨
我们还推出了一个新的 API,以支持在现代边缘运行环境(如 Deno 和 Cloudflare workers)中使用 Suspense 的流式 SSR:
🌐 We’re also introducing a new API to support streaming SSR with Suspense for modern edge runtime environments, such as Deno and Cloudflare workers:
renderToReadableStream:新 ✨
以下 APIs 将继续工作,但对 Suspense 的支持有限:
🌐 The following APIs will continue working, but with limited support for Suspense:
renderToString:有限 ⚠️renderToStaticMarkup:有限 ⚠️
最后,这个 API 将继续用于渲染电子邮件:
🌐 Finally, this API will continue to work for rendering e-mails:
renderToStaticNodeStream
有关服务器渲染 API 变更的更多信息,请参阅工作组发表的帖子 在服务器上升级到 React 18、对新的 Suspense SSR 架构的深入解析 以及 Shaundai Person 在 React Conf 2021 上关于 使用 Suspense 的流式服务器渲染 的演讲。
🌐 For more information on the changes to server rendering APIs, see the working group post on Upgrading to React 18 on the server, a deep dive on the new Suspense SSR Architecture, and Shaundai Person’s talk on Streaming Server Rendering with Suspense at React Conf 2021.
TypeScript 定义的更新
🌐 Updates to TypeScript definitions
如果你的项目使用 TypeScript,你需要将你的 @types/react 和 @types/react-dom 依赖更新到最新版本。新的类型更安全,并能捕捉以前类型检查器会忽略的问题。最显著的变化是 children 属性现在在定义属性时需要显式列出,例如:
🌐 If your project uses TypeScript, you will need to update your @types/react and @types/react-dom dependencies to the latest versions. The new types are safer and catch issues that used to be ignored by the type checker. The most notable change is that the children prop now needs to be listed explicitly when defining props, for example:
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}请参阅 React 18 类型定义拉取请求 以查看仅类型的所有更改列表。它链接到库类型中的示例修复,因此你可以看到如何调整代码。你可以使用 自动迁移脚本 来帮助更快地将应用代码迁移到新的、更安全的类型定义。
🌐 See the React 18 typings pull request for a full list of type-only changes. It links to example fixes in library types so you can see how to adjust your code. You can use the automated migration script to help port your application code to the new and safer typings faster.
如果你在类型定义中发现错误,请在 DefinitelyTyped 仓库中提交一个问题。
🌐 If you find a bug in the typings, please file an issue in the DefinitelyTyped repo.
自动批处理
🌐 Automatic Batching
React 18 通过默认进行更多批处理来提升开箱即用的性能。批处理是指 React 将多个状态更新组合到一次重新渲染中,以获得更好的性能。在 React 18 之前,我们只在 React 事件处理程序内部进行批处理。承诺(promises)、setTimeout、本地事件处理程序或任何其他事件内的更新默认情况下不会在 React 中进行批处理:
🌐 React 18 adds out-of-the-box performance improvements by doing more batching by default. Batching is when React groups multiple state updates into a single re-render for better performance. Before React 18, we only batched updates inside React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default:
// 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 事件中的更新以相同的方式合并:
🌐 Starting in React 18 with createRoot, all updates will be automatically batched, no matter where they originate from. This means that updates inside of timeouts, promises, native event handlers or any other event will batch the same way as updates inside of React events:
// 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:
🌐 This is a breaking change, but we expect this to result in less work rendering, and therefore better performance in your applications. To opt-out of automatic batching, you can use 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
}有关更多信息,请参阅 自动批处理深入解析。
🌐 For more information, see the Automatic batching deep dive.
库的新 API
🌐 New APIs for Libraries
在 React 18 工作组中,我们与库维护者合作,创建了支持并发渲染所需的新 API,以满足他们在样式和外部存储等字段特定用例的需求。为了支持 React 18,一些库可能需要切换到以下某个 API:
🌐 In the React 18 Working Group we worked with library maintainers to create new APIs needed to support concurrent rendering for use cases specific to their use case in areas like styles, and external stores. To support React 18, some libraries may need to switch to one of the following APIs:
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,我们在 发布文章 中有更多分享。
🌐 React 18 also introduces new APIs for concurrent rendering such as startTransition, useDeferredValue and useId, which we share more about in the release post.
严格模式更新
🌐 Updates to Strict Mode
将来,我们希望添加一个功能,使 React 能够在保留状态的同时添加和移除 UI 的各个部分。例如,当用户从一个屏幕切换到另一个屏幕再返回时,React 应该能够立即显示之前的屏幕。为此,React 将使用之前相同的组件状态卸载并重新挂载树。
🌐 In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before.
此功能将使 React 的性能开箱即用地更好,但要求组件能够适应效果被多次挂载和销毁。大多数效果无需任何更改即可正常工作,但有些效果假设它们只会被挂载或销毁一次。
🌐 This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects assume they are only mounted or destroyed once.
为了帮助发现这些问题,React 18 在严格模式中引入了一种仅用于开发的新检查。这个新检查会在组件首次挂载时自动卸载并重新挂载每个组件,并在第二次挂载时恢复之前的状态。
🌐 To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
在此更改之前,React 会挂载组件并创建副作用:
🌐 Before this change, React would mount the component and create the effects:
* React mounts the component.
* Layout effects are created.
* Effect effects are created.在 React 18 的严格模式下,React 会在开发模式中模拟组件的卸载和重新挂载:
🌐 With Strict Mode in React 18, React will simulate unmounting and remounting the component in development mode:
* 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有关更多信息,请参阅工作组关于《向 StrictMode 添加可重用状态》(Adding Reusable State to StrictMode) 和 《如何在 Effects 中支持可重用状态》(How to support Reusable State in Effects) 的帖子。
🌐 For more information, see the Working Group posts for Adding Reusable State to StrictMode and How to support Reusable State in Effects.
配置你的测试环境
🌐 Configuring Your Testing Environment
当你首次更新测试以使用 createRoot 时,你可能会在测试控制台中看到此警告:
🌐 When you first update your tests to use createRoot, you may see this warning in your test console:
要解决此问题,请在运行测试之前将 globalThis.IS_REACT_ACT_ENVIRONMENT 设置为 true:
🌐 To fix this, set globalThis.IS_REACT_ACT_ENVIRONMENT to true before running your test:
// In your test setup file
globalThis.IS_REACT_ACT_ENVIRONMENT = true;这个标志的目的是告诉 React 它正在运行在类似单元测试的环境中。如果你忘记用 act 封装一次更新,React 会记录有用的警告。
🌐 The purpose of the flag is to tell React that it’s running in a unit test-like environment. React will log helpful warnings if you forget to wrap an update with act.
你也可以将标志设置为 false 来告诉 React 不需要 act。这对于模拟完整浏览器环境的端到端测试可能很有用。
🌐 You can also set the flag to false to tell React that act isn’t needed. This can be useful for end-to-end tests that simulate a full browser environment.
最终,我们预计测试库将自动为你配置此项。例如,下一版本的 React 测试库已经内置对 React 18 的支持,无需任何额外配置。
🌐 Eventually, we expect testing libraries will configure this for you automatically. For example, the next version of React Testing Library has built-in support for React 18 without any additional configuration.
有关 act 测试 API 及相关更改的更多背景 可在工作组中获得。
停止支持 Internet Explorer
🌐 Dropping Support for Internet Explorer
在此版本中,React 将停止对 Internet Explorer 的支持,该浏览器将于 2022 年 6 月 15 日停止支持。我们现在进行此更改是因为 React 18 引入的新功能是使用现代浏览器特性构建的,例如微任务,而这些特性在 IE 中无法得到充分的 polyfill 支持。
🌐 In this release, React is dropping support for Internet Explorer, which is going out of support on June 15, 2022. We’re making this change now because new features introduced in React 18 are built using modern browser features such as microtasks which cannot be adequately polyfilled in IE.
如果你需要支持 Internet Explorer,我们建议你继续使用 React 17。
🌐 If you need to support Internet Explorer we recommend you stay with React 17.
弃用
🌐 Deprecations
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已被弃用。
其他重大变更
🌐 Other Breaking Changes
- 一致的 useEffect 时机:如果更新是在离散的用户输入事件(例如点击或按键事件)期间触发的,React 现在总是同步刷新 effect 函数。以前,这种行为并不总是可预测或一致的。
- 更严格的水化错误:由于缺失或多余的文本内容而导致的水化不匹配现在被视为错误,而不是警告。React 不再尝试通过在客户端插入或删除节点来“修补”单个节点以匹配服务器标记,而是会回退到树中最近的
<Suspense>边界进行客户端渲染。这确保了水化后的树结构一致,并避免了由水化不匹配可能导致的潜在隐私和安全漏洞。 - Suspense 树始终是一致的: 如果一个组件在完全添加到树之前被挂起,React 不会以不完整的状态将其添加到树中,也不会触发它的副作用。相反,React 会完全丢弃新的树,等待异步操作完成,然后从头重新尝试渲染。React 会并发地渲染重试尝试,并且不会阻塞浏览器。
- 带有 Suspense 的布局效果:当一个组件树重新挂起并回退到回退内容时,React 现在会清理布局效果,然后在边界内的内容再次显示时重新创建它们。这解决了一个问题,即在与 Suspense 一起使用时,组件库无法正确测量布局。
- 新的 JS 环境要求:React 现在依赖于现代浏览器功能,包括
Promise、Symbol和Object.assign。如果你需要支持旧浏览器和设备,例如不提供现代浏览器功能或实现不符合规范的 Internet Explorer,请考虑在打包的应用中包含全局 polyfill。
其他显著变化
🌐 Other Notable Changes
React
- 组件现在可以渲染
undefined: 如果从组件返回undefined,React 不再发出警告。这使得允许的组件返回值与组件树中间允许的值保持一致。我们建议使用 linter 来防止像忘记在 JSX 前添加return语句这样的错误。 - 在测试中,
act警告现在是可选择的: 如果你正在运行端到端测试,act警告是没有必要的。我们引入了一个可选择机制,这样你可以仅在单元测试中启用这些警告,在那里它们是有用且有益的。 - 关于未挂载组件的
setState不再有警告: 以前,当你在未挂载组件上调用setState时,React 会发出内存泄漏的警告。这个警告是为订阅而添加的,但人们主要在设置状态没有问题的场景中遇到它,并且解决方法会让代码变得更糟。我们已经移除了这个警告。 - 不再抑制控制台日志: 当你使用严格模式时,React 会渲染每个组件两次,以帮助你发现意外的副作用。在 React 17 中,我们对两次渲染中的一次抑制了控制台日志,以便日志更易于阅读。针对社区反馈关于这一行为令人困惑的问题,我们已移除了该抑制。相反,如果你安装了 React 开发者工具,第二次日志的渲染将以灰色显示,并且可以选择(默认关闭)完全抑制这些日志。
- 改进的内存使用: React 现在在卸载时会清理更多的内部字段,从而减少应用代码中可能存在的未修复内存泄漏的影响。
React DOM 服务器
🌐 React DOM Server
renderToString: 当在服务器上挂起时将不再出错。相反,它会为最近的<Suspense>边界生成备用 HTML,然后在客户端重试渲染相同的内容。仍然建议你改用像renderToPipeableStream或renderToReadableStream这样的流式 API。renderToStaticMarkup: 在服务器挂起时将不再出错。相反,它将为最近的<Suspense>边界发出回退 HTML。
更新日志
🌐 Changelog
你可以在此查看完整更新日志。
🌐 You can view the full changelog here.