以下为 React 18 官方博客 的翻译
React 18 现在可以在 npm 上使用了!
在上一篇文章中,我们分享了将你的应用程序升级到 React 18 的分步说明。在这篇文章中,我们将概述 React 18 的新内容,以及它对未来的意义。
我们最新的主要版本包括开箱即用的改进,如自动批处理,新的 API 如 startTransition ,以及支持 Suspense 的 streaming server-side rendering (流式服务器渲染)。
React 18 中的许多功能都是建立在我们新的并发渲染器之上的,这种幕后变化释放了强大的新功能。Concurrent React 是可选的 - 只有当你使用并发功能时才会启用 - 但我们认为它将对人们构建应用程序的方式产生重大影响。
我们花了数年时间研究和开发对 React 并发的支持,我们还特别为现有用户提供一个渐进的采用路径。去年夏天,我们成立了React 18 工作组,以收集来自社区专家的反馈,并确保整个 React 生态系统的顺利升级体验。
如果你错过了,我们在 2021 年 React Conf 上分享了很多这个愿景:
下面是对这个版本的完整概述,从并发渲染开始。
React Native 用户请注意。React 18 将与新的 React Native 架构一起提供给 React Native 。更多信息,请看React Conf 的主题演讲。
React 18 中最重要的一点是,我们希望您永远不必考虑:并发性。我们认为这对应用程序开发人员来说基本上是正确的,尽管对库维护者来说,故事可能有点复杂。
并发本身并不是一个功能。它是一种新的幕后机制,使 React 能够同时准备多个版本的 UI 。你可以把并发看作是一个实现细节-它的价值在于它所解锁的功能。React 在其内部实现中使用了复杂的技术,比如优先级队列和多重缓冲。但你不会在我们的公共 API 中看到这些概念。
当我们设计 API 时,我们试图向开发者隐藏实现细节。作为一个 React 开发者,你专注于你想要的用户体验,而 React 处理如何提供这种体验。因此,我们不期望 React 开发者知道并发性在幕后是如何工作的。
然而,Concurrent React 比典型的实现细节更重要 - 它是 React 核心渲染模型的基础性更新。因此,虽然知道并发是如何工作的并不是非常重要,但在高层次上了解它可能是值得的。
Concurrent React 的一个关键特性是渲染是可中断的。首次升级到 React 18 时,在添加任何并发功能之前,更新的呈现方式与 React 的早期版本相同——在一个单一、不间断、同步的事务中。使用同步渲染,一旦更新开始渲染,在用户可以在屏幕上看到结果之前,任何东西都不能中断它。
在并发渲染中,情况并非总是如此。React 可能会开始渲染一个更新,在中间停顿一下,然后再继续。它甚至可能完全放弃一个正在进行的渲染。React 保证,即使渲染被打断,用户界面也会显示一致。为了做到这一点,React 要等整个树完成评估( evaluated ),它他才会执行 DOM 突变。有了这种能力,React 可以在后台准备新的屏幕而不阻塞主线程。这意味着 UI 可以立即响应用户的输入,即使它正处于一个大型的渲染任务中,创造一个流畅的用户体验。
另一个例子是可重用状态。Concurrent React 可以从屏幕上删除部分用户界面,然后在以后重新使用的状态时将它们添加回来。例如,当用户按 Tab 键离开当前页面并返回时,React 能够将页面恢复到与之前相同的状态。在即将到来的次要版本中,我们计划添加一个名为 <OffScreen> 的新组件来实现此模式。类似地,您将能够使用屏幕外在后台准备新的 UI ,以便在用户显示它之前准备好。
并发渲染是 React 中一个强大的新工具,我们的大多数新功能都是为了利用它而建立的,包括 Suspense 、transitions 和 streaming server rendering 。但 React 18 只是我们在这个新基础上所要做的事情的开始。
从技术上讲,并发渲染是一个突破性的变化。因为并发渲染是可中断的,当它被启用时,组件的行为会略有不同。
在我们的测试中,我们已经将成千上万的组件升级到 React 18 。我们发现,几乎所有的现有组件都能在并发渲染下 "正常工作",没有任何变化。然而,其中一些可能需要一些额外的迁移工作。虽然这些变化通常很小,但你仍然有能力按照自己的节奏进行。React 18 中的新渲染行为只在你的应用程序中使用新功能的部分启用。
整体的升级策略是让你的应用在 React 18 上运行而不破坏现有的代码。然后你可以按照自己的节奏逐渐开始添加并发功能。你可以使用 来帮助在开发过程中发现与并发相关的错误。严格模式不影响生产行为,但在开发过程中,它将记录额外的警告,并重复调用那些预计是空闲的函数。它不会发现一切,但它能有效地防止最常见的错误类型。
在你升级到 React 18 之后,你将能够立即开始使用并发功能。例如,你可以使用 startTransition 在屏幕之间进行导航,而不阻止用户输入。或者使用 DeferredValue 来节制昂贵的重新渲染。
然而,从长远来看,我们希望你为你的应用程序添加并发功能的主要方式是使用一个支持并发的库或框架。在大多数情况下,你不会直接与并发的 API 交互。例如,开发者在导航到一个新的屏幕时不再调用 startTransition ,路由器库会自动将导航包裹在 startTransition 中。
库升级到兼容并发可能需要一些时间。我们已经提供了新的 API ,使库更容易利用并发功能。同时,在我们努力逐步迁移 React 生态系统的过程中,请对维护者保持耐心。
更多信息,请看我们之前的文章。如何升级到 React 18。
在 React 18 中,你可以开始在 Relay 、Next.js 、Hydrogen 或 Remix 等 opinionated 框架中使用 Suspense 进行数据获取。使用 Suspense 进行 hoc data fetching 在技术上是可行的,但仍不建议作为一般策略。
在未来,我们可能会公开更多的原语( primitives ),让你更容易用 Suspense 访问你的数据,也许不需要使用 opinionated 框架。然而,当 Suspense 被深度整合到你的应用程序的架构中时,它的效果是最好的:你的路由器、你的数据层和你的服务器渲染环境。因此,从长远来看,我们预计库和框架将在 React 生态系统中发挥关键作用。
和以前的 React 版本一样,你也可以用 Suspense 在客户端用 React.lazy 进行代码分割。但我们对 Suspense 的愿景一直是远远超过加载代码 - 目标是扩展对 Suspense 的支持,以便最终,同样的 Suspense 可以处理任何异步操作(加载代码、数据、图像等)。
Server Components 是一个即将推出的功能,它允许开发人员建立跨越服务器和客户端的应用程序,将客户端应用程序的丰富互动性与传统服务器渲染的性能相结合。服务器组件在本质上并不与 并发 React 耦合,但它被设计为与并发功能(如 Suspense 和 streaming server rendering )配合使用效果最佳。
服务器组件仍然是实验性的,但我们希望在 18.x 小版本中发布一个初始版本。同时,我们正在与 Next.js 、Hydrogen 和 Remix 等框架合作,以推进该提案,并使其准备好被广泛采用。
批处理是指 React 将多个状态更新分组到一个重新渲染中,以获得更好的性能。如果没有自动批处理,我们只对 React 事件处理程序内的更新进行批处理。默认情况下,React 不会对 promises 、setTimeout 、native event handlers 或任何其他事件中的更新进行批处理。有了自动批处理,这些更新将被自动批处理。
// 之前: 只有 React 事件被批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 将渲染两次,每次状态更新一次(无批处理)
}, 1000);
// 之后: 在 promises 、setTimeout 、native event handlers 中都会被批处理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React 在最后只会重新渲染一次(这就是批处理!)
}, 1000);
更多信息,请看这篇关于 React 18 中自动批处理以减少渲染的文章。
Transitions (过渡) 是 React 的一个新概念,用于区分紧急和非紧急更新。
像 输入文字、点击或按压 这样的紧急更新,需要立即响应,以符合我们对物理对象行为方式的直觉。否则他们就会感觉 "不对劲"。然而,转换是不同的,因为用户并不期望在屏幕上看到每个中间值。
例如,当您在下拉列表中选择一个筛选器时,您希望当您单击时,筛选器按钮本身会立即响应。然而,实际结果可能会单独过渡。一个小小的延迟将是不可察觉的,而且通常是意料之中的。如果在渲染结果之前再次更改过滤器,则只需查看最新结果。
通常情况下,为了获得最佳的用户体验,一个用户的输入应该同时导致一个紧急的更新和一个非紧急的更新。你可以在输入事件中使用 startTransition API 来告知 React 哪些是紧急更新,哪些是 "过渡"。
import {startTransition} from 'react';
// 紧急:显示键入的内容
setInputValue(input);
// 将内部的任何状态更新标记为 过渡
startTransition(() => {
// 过渡:显示结果
setSearchQuery(input);
});
被 startTransition 包裹的更新被当作非紧急事件来处理,如果有更紧急的更新,如点击或按键,则会被打断。如果一个过渡被用户打断(例如,连续输入多个字符),React 会扔掉未完成的的渲染工作,只渲染最新的更新。
过渡 将选择进入并发渲染,这允许更新被中断。如果内容重新暂停,过渡 也会告诉 React 继续显示当前内容,同时在后台渲染过渡内容(详见Suspense RFC)。
如果组件树的某个部分还没有准备好被显示,Suspense 可以让你声明性地指定它的加载状态。
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
Suspense 使 "UI 加载状态 "成为 React 编程模型中的第一类声明性概念。这让我们可以在它上面建立更高层次的功能。
几年前,我们推出了一个有限的 Suspense 版本。然而,唯一支持的用例是用 React.lazy 拆分代码,而且在服务器上渲染时根本不支持。
在 React 18 中,我们增加了对服务器上的 Suspense 的支持,并使用并发渲染功能扩展了其功能。
React 18 中的 Suspense 在与过渡 API 结合时效果最好。如果你在过渡期间暂停,React 将防止已经可见的内容被 fallback 取代。相反,React 会延迟渲染,直到有足够的数据加载,以防止出现糟糕的加载状态。
更多内容请参见 React 18 中的 RFC for Suspense。
在这个版本中,我们利用机会重新设计了我们为客户端和服务器上的渲染所暴露的 API 。这些变化允许用户继续使用 React 17 模式下的旧 API ,同时升级到 React 18 的新 API 。
React DOM 客户端
这些新的 API 现在都是从 react-dom/client 导出的。
createRoot 和 hydrateRoot 都接受一个新的选项,叫做 onRecoverableError ,以防你想在 React 从渲染或 hydrate 过程中发生错误时得到通知,以便 log 。默认情况下,React 会使用reportError,或者在旧的浏览器中使用 console.error 。
React DOM 服务器
这些新的 API 现在从 react-dom/server 导出,并且完全支持服务器上的 streaming Suspense 。
现有的 renderToString 方法继续工作,但不鼓励使用。
在未来,我们希望增加一个功能,允许 React 在保留状态的同时增加和删除 UI 的部分。例如,当用户从一个屏幕切换到另一个屏幕时,React 应该能够立即显示之前的屏幕。要做到这一点,React 将使用与之前相同的组件状态来卸载和重新装载树。
这个功能将给 React 应用带来更好的开箱即用的性能,但需要组件对效果被多次挂载和销毁有弹性。大多数 effects 将在没有任何变化的情况下工作,但有些 effects 假设它们只被挂载或销毁一次。
为了帮助发现这些问题,React 18 为严格模式引入了一个新的仅用于开发的检查。这个新的检查将自动卸载并重新挂载每个组件,每当一个组件第一次挂载时,在第二次挂载时恢复之前的状态。
在这个,React 会挂载组件并创建 effects 。
* React 挂载组件。
* Layout effects 被创建
* Effects 被创建
在 React 18 的严格模式下,React 将模拟在开发模式下卸载和重新安装组件:
* React 挂载组件。
* Layout effects 被创建
* Effects 被创建
* React 模拟卸载该组件。
* Layout effects 被销毁
* Effects 被销毁
* React 模拟用以前的状态挂载组件。
* Layout effects 被创建
* Effects 被创建
See docs for ensuring resusable state here.
useId
useId 是一个新的 hooks ,用于在客户端和服务器上生成唯一的 ID ,同时避免了 hydrate 不匹配。它主要适用于与需要唯一 ID 的可访问性 API 集成的组件库。这解决了一个在 React 17 及以下版本中已经存在的问题,但在 React 18 中更加重要,因为新的 streaming server renderer 是不按顺序地渲染 HTML 的。请看这里的文档。
useTransition
useTransition 和 startTransition 让你把一些状态更新标记为不紧急。其他状态更新在默认情况下被认为是紧急的。React 将允许紧急状态更新(例如,更新一个文本输入)打断非紧急状态更新(例如,渲染搜索结果列表)。请看这里的文档
useDeferredValue
useDeferredValue 让你延迟重新渲染树的一个非紧急部分。它类似于 debouncing ,但与之相比有一些优势。没有固定的时间延迟,所以 React 会在第一次渲染反映在屏幕上后立即尝试延迟渲染。延迟渲染是可中断的,不会阻止用户输入。请看这里的文档。
useSyncExternalStore
useSyncExternalStore 是一个新的 hooks ,它允许外部存储支持并发读取,强制更新到存储是同步的。在实现对外部数据源的订阅时,它消除了对 useEffect 的需求,并被推荐给任何与 React 外部状态集成的库。请看这里的文档。
useSyncExternalStore 的目的是供库使用,而不是供应用程序代码使用。
useInsertionEffect
useInsertionEffect 是一个新的 hooks ,允许 CSS-in-JS 库解决在渲染中注入样式的性能问题。除非你要写一个 CSS-in-JS 库,否则我们不希望你使用这个。这个钩子将在 DOM 被突变后运行,但在布局效果读取新的布局之前。这解决了一个在 React 17 及以下版本中已经存在的问题,但在 React 18 中更加重要,因为 React 在并发渲染时向浏览器让步,给它一个重新计算布局的机会。请看这里的文档。
useInsertionEffect 的目的是供库使用,而不是供应用程序代码使用。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.