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