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 正在进行大型渲染任务,它也可以立即响应用户输入,从而创造流畅的用户体验。
另一个例子是可重用状态。并发 React 可以从屏幕中删除 UI 的部分,然后在重用之前状态的同时将它们添加回来。例如,当用户从一个屏幕切换到另一个屏幕,然后返回时,React 应该能够以之前状态恢复之前的屏幕。在即将发布的次要版本中,我们计划添加一个名为<Offscreen>
的新组件来实现此模式。类似地,您将能够使用 Offscreen 在后台准备新的 UI,以便在用户显示它之前准备好。
并发渲染是 React 中一个强大的新工具,我们的大多数新功能都是为了利用它而构建的,包括 Suspense、过渡和流式服务器端渲染。但 React 18 只是我们希望在这个新基础上构建的开始。
逐步采用并发功能
从技术上讲,并发渲染是一个重大更改。由于并发渲染是可以中断的,因此组件在启用它时行为略有不同。
在我们的测试中,我们已经将数千个组件升级到 React 18。我们发现几乎所有现有组件在没有进行任何更改的情况下都可以与并发渲染“正常工作”。但是,其中一些可能需要一些额外的迁移工作。尽管更改通常很小,但您仍然可以按照自己的节奏进行更改。React 18 中的新渲染行为仅在使用新功能的应用程序部分启用。
总体升级策略是在不破坏现有代码的情况下让您的应用程序在 React 18 上运行。然后您可以按照自己的节奏逐步开始添加并发功能。您可以使用<StrictMode>
帮助在开发过程中发现与并发相关的错误。严格模式不会影响生产行为,但在开发过程中它会记录额外的警告并双重调用预期是幂等的函数。它不会捕获所有内容,但对于防止最常见的错误类型非常有效。
升级到 React 18 后,您将能够立即开始使用并发功能。例如,您可以使用 startTransition 在不阻塞用户输入的情况下在屏幕之间导航。或者使用 DeferredValue 来限制昂贵的重新渲染。
但是,从长远来看,我们预计您向应用程序添加并发的主要方式是使用支持并发的库或框架。在大多数情况下,您不会直接与并发 API 交互。例如,路由库将自动将导航包装在 startTransition 中,而不是让开发人员在每次导航到新屏幕时都调用 startTransition。
库升级以支持并发可能需要一些时间。我们提供了新的 API 使库更轻松地利用并发功能。在此期间,请耐心等待维护人员,因为我们正在努力逐步迁移 React 生态系统。
有关更多信息,请参阅我们之前的帖子:如何升级到 React 18。
数据框架中的 Suspense
在 React 18 中,您可以开始使用 Suspense 来在 Relay、Next.js、Hydrogen 或 Remix 等有主见的框架中进行数据获取。虽然 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 中的自动批处理以减少渲染次数 的这篇文章。
新功能:过渡
过渡是 React 中一个新概念,用于区分紧急更新和非紧急更新。
- 紧急更新 反映直接交互,例如键入、单击、按下等。
- 过渡更新 将 UI 从一个视图过渡到另一个视图。
紧急更新(例如键入、单击或按下)需要立即响应,以符合我们对物理物体行为的直觉。否则,它们会让人感觉“不对”。但是,过渡不同,因为用户不希望在屏幕上看到每个中间值。
例如,当您在下拉菜单中选择一个筛选器时,您希望筛选器按钮本身在您单击时立即做出响应。但是,实际结果可能会单独过渡。短暂的延迟将难以察觉,而且通常是可以预期的。如果您在结果完成渲染之前再次更改筛选器,您只关心看到最新的结果。
通常,为了获得最佳用户体验,单个用户输入应该会导致紧急更新和非紧急更新。您可以在输入事件中使用 startTransition API 来通知 React 哪些更新是紧急的,哪些是“过渡”。
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 包装的更新将被视为非紧急更新,如果出现更多紧急更新(例如单击或按键按下),它们将被中断。如果过渡被用户中断(例如,通过连续键入多个字符),React 将丢弃未完成的陈旧渲染工作,只渲染最新的更新。
useTransition
: 一个 Hook 用于启动过渡,包括一个用于跟踪待处理状态的值。startTransition
: 一个方法,当无法使用 Hook 时用于启动过渡。
过渡将选择加入并发渲染,这允许更新被中断。如果内容重新挂起,过渡也会告诉 React 在后台渲染过渡内容的同时继续显示当前内容(有关更多信息,请参阅 Suspense RFC)。
新的 Suspense 功能
Suspense 允许您声明性地指定组件树一部分的加载状态,如果该部分尚未准备好显示。
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
Suspense 使“UI 加载状态”成为 React 编程模型中的一个一等声明式概念。这使我们能够在此基础上构建更高级的功能。
我们几年前引入了 Suspense 的一个有限版本。但是,唯一支持的用例是使用 React.lazy 进行代码拆分,并且在服务器上渲染时完全不支持它。
在 React 18 中,我们添加了对服务器上 Suspense 的支持,并使用并发渲染功能扩展了其功能。
React 18 中的 Suspense 在与过渡 API 结合使用时效果最佳。如果您在过渡期间挂起,React 将阻止已经可见的内容被备用内容替换。相反,React 将延迟渲染,直到加载了足够的数据以防止出现不良的加载状态。
有关更多信息,请参阅有关 React 18 中的 Suspense 的 RFC。
新的客户端和服务器渲染 API
在这个版本中,我们借此机会重新设计了我们在客户端和服务器上渲染时公开的 API。这些更改允许用户在他们升级到 React 18 中的新 API 时继续使用 React 17 模式下的旧 API。
React DOM 客户端
这些新 API 现在从 react-dom/client
中导出
createRoot
: 用于创建根节点的新方法,用于render
或unmount
。请使用它代替ReactDOM.render
。React 18 中的新功能无法在没有它的情况下使用。hydrateRoot
: 用于将服务器渲染的应用程序进行水合的新方法。请使用它代替ReactDOM.hydrate
,并结合使用新的 React DOM Server API。React 18 中的新功能无法在没有它的情况下使用。
Both createRoot
和 hydrateRoot
接受一个名为 onRecoverableError
的新选项,如果您希望在 React 从渲染或水合过程中的错误恢复时收到通知以进行日志记录,可以使用它。默认情况下,React 将使用 reportError
,或者在较旧的浏览器中使用 console.error
。
React DOM Server
这些新 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,同时避免水合不匹配。它主要用于与需要唯一 ID 的辅助功能 API 集成的组件库。这解决了一个在 React 17 及以下版本中已经存在的问题,但在 React 18 中由于新的流式服务器渲染器如何以乱序方式提供 HTML,因此这个问题变得更加重要。 此处查看文档。
注意
useId
不是用于生成 列表中的键。键应从您的数据中生成。
useTransition
useTransition
和 startTransition
允许您将某些状态更新标记为非紧急。默认情况下,其他状态更新被视为紧急。React 将允许紧急状态更新(例如,更新文本输入)中断非紧急状态更新(例如,渲染搜索结果列表)。 此处查看文档。
useDeferredValue
useDeferredValue
允许您延迟重新渲染树的非紧急部分。它类似于防抖,但与之相比有一些优势。没有固定的时间延迟,因此 React 将尝试在第一次渲染反映到屏幕后立即执行延迟渲染。延迟渲染是可以中断的,并且不会阻止用户输入。 此处查看文档。
useSyncExternalStore
useSyncExternalStore
是一个新的 Hook,它允许外部存储通过强制对存储的更新为同步的方式来支持并发读取。它消除了在实现对外部数据源的订阅时对 useEffect 的需求,并且建议用于任何与 React 外部状态集成的库。 在此处查看文档.
注意
useSyncExternalStore
旨在供库使用,而不是应用程序代码。
useInsertionEffect
useInsertionEffect
是一个新的 Hook,它允许 CSS-in-JS 库解决在 render 中注入样式的性能问题。 除非您已经构建了 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 by @acdlite, @lunaruan, @rickhanlonii, 和 @sebmarkbage) - 添加
useId
以生成唯一的 ID。 (#17322, #18576, #22644, #22672, #21260 by @acdlite, @lunaruan, 和 @sebmarkbage) - 添加
useSyncExternalStore
以帮助外部存储库与 React 集成。 (#15022, #18000, #18771, #22211, #22292, #22239, #22347, #23150 by @acdlite, @bvaughn, 和 @drarmstr) - 添加
startTransition
作为useTransition
的一个版本,没有挂起的反馈。(#19696 by @rickhanlonii) - 为 CSS-in-JS 库添加
useInsertionEffect
。(#21913 by @rickhanlonii) - 当内容重新出现时,使 Suspense 重新挂载布局效果。(#19322, #19374, #19523, #20625, #21079 by @acdlite, @bvaughn, and @lunaruan)
- 使
<StrictMode>
重新运行效果以检查可恢复状态。(#19523 , #21418 by @bvaughn and @lunaruan) - 假设符号始终可用。(#23348 by @sebmarkbage)
- 删除
object-assign
polyfill。(#23351 by @sebmarkbage) - 删除不受支持的
unstable_changedBits
API。(#20953 by @acdlite) - 允许组件渲染未定义。(#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
观察错误道具的错误。(#22445 by @josephsavona) - 修复在 Safari 中追加 iframe 时,
setState
被忽略的错误。(#23111 by @gaearon) - 修复在树中渲染
ZonedDateTime
时发生的崩溃。(#20617 by @dimaqq) - 修复在测试中将文档设置为
null
时发生的崩溃。(#22695 by @SimenB) - 修复在启用并发功能时,
onLoad
不会触发的错误。(#23316 by @gnoff) - 修复当选择器返回
NaN
时出现的警告。 (#23333 由 @hachibeeDI 提交) - 修复在测试中将文档设置为
null
时发生的崩溃。(#22695 by @SimenB) - 修复生成的许可证头。 (#23004 由 @vitaliemiron 提交)
- 将
package.json
添加为入口点之一。 (#22954 由 @Jack 提交) - 允许在 Suspense 边界之外进行暂停。 (#23267 由 @acdlite 提交)
- 每当水合失败时记录可恢复的错误。 (#23319 由 @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 by @sebmarkbage) - 修复
aspectRatio
样式未应用的问题。 (#21100 by @gaearon) - 如果调用了
renderSubtreeIntoContainer
,则发出警告。 (#23355 by @acdlite)
React DOM Server
- 添加新的流式渲染器。 (#14144, #20970, #21056, #21255, #21200, #21257, #21276, #22443, #22450, #23247, #24025, #24030 by @sebmarkbage)
- 修复处理多个请求时 SSR 中的上下文提供程序。 (#23171 by @frandiox)
- 当文本不匹配时恢复到客户端渲染。 (#23354 by @acdlite)
- 弃用
renderToNodeStream
。 (#23359 by @sebmarkbage) - 修复新服务器渲染器中的虚假错误日志。 (#24043 by @eps1lon)
- 修复新服务器渲染器中的一个错误。 (#22617 by @shuding)
- 忽略服务器上自定义元素内的函数和符号值。 (#21157 by @sebmarkbage)
React DOM 测试工具
- 当在生产环境中使用
act
时抛出异常。 (#21686 by @acdlite) - 支持使用
global.IS_REACT_ACT_ENVIRONMENT
禁用虚假 act 警告。 (#22561 by @acdlite) - 扩展 act 警告以涵盖所有可能安排 React 工作的 API。 (#22607 by @acdlite)
- 使
act
批量更新。 (#21797 by @acdlite) - 删除悬挂被动效果的警告。 (#22609 by @acdlite)
React 刷新
- 在快速刷新中跟踪延迟安装的根节点。 (#22740 by @anc95)
- 在
package.json
文件中添加exports
字段。 (#23087 由 @otakustay 提交)