React 18新功能,新特性!React 18发布!

React最新版本,React更新,React 18新功能,新特性,包括开箱即用的改进,如自动批处理、startTransition 等新 API,以及支持 Suspense 的流式服务器端渲染,其中最重要的新增功能则是并发性。

React 18新功能,新特性!React 18发布!

React 18 现在可以从 npm 安装了!

在过去的文章中,我们分享了将您的应用程序升级到 React 18 的分步说明。在这篇文章中,我们将概述 React 18 的新特性以及它对未来的意义。

最新的主要版本包括开箱即用的改进,如自动批处理、startTransition 等新 API,以及支持 Suspense 的流式服务器端渲染。

React 18 中的许多功能都建立在我们新的并发渲染器之上,这是一个解锁强大新功能的幕后更改。React并发是可选的——它仅在你使用并发特性时启用——但我们认为它会对人们构建应用程序的方式产生重大影响。

我们花了数年时间研究和开发对 React 并发特性的支持,并且我们特别注意为现有用户提供逐步采用的路径。去年夏天,我们成立了 React 18 工作组,收集社区专家的反馈,确保整个 React 生态系统的顺利升级体验。

我们在 React Conf 2021 上分享了很多这样的愿景:

以下是此版本中预期内容的完整概述,从并发渲染开始。React Native用户注意事项:React18 将在带有新React Native架构的React Native中发布。有关更多信息,请参阅此处的 React Conf 主题演讲


什么是React的并发性?

React 18 中最重要的新增功能是我们希望您永远不必考虑的:并发性。我们认为这对于应用程序开发人员来说基本上是正确的,尽管对于维护人员来说这个故事可能有点复杂。

并发本身不是一个特性。这是一种新的幕后机制,使 React 能够同时准备多个版本的 用户界面。您可以将并发视为一个实现细节——它之所以有价值,是因为它解锁了一些功能。 React 在其内部实现中使用了复杂的技术,例如优先级队列和多重缓冲。但是您不会在我们的公共 API 中的任何地方看到这些概念。

当我们设计 API 时,我们的开发人员尽力隐藏实现细节。作为一名 React 开发人员,您通常专注于用户体验的开发,而 React 负责处理如何提供这种体验。所以我们不希望 React 开发人员花太多的精力去知道并发是如何工作的。

然而,React并发特性比一般的特性细节更重要——它是对 React 核心渲染模型的基础更新。因此,虽然了解并发的工作原理并不是非常重要,但知道它作为高级别特性本身这件事更值得了解。

React并发的一个关键属性是渲染是可中断的。当您第一次升级到 React 18 时,在添加任何并发功能之前,更新的呈现方式与之前版本的 React 相同——在单个、不间断的同步事务中。使用同步渲染,一旦更新开始渲染,在用户可以在屏幕上看到结果之前,没有任何东西可以中断它。

在并发渲染中,情况并非总是如此。 React 可能会开始渲染更新,在中间暂停,然后再继续。它甚至可能完全放弃正在进行的渲染。 React 保证即使渲染被中断,UI 也会保持一致。为此,它会等待执行 DOM 变化,直到完成整个DOM树的评估。有了这个能力,React 可以在后台准备新的屏幕而不阻塞主线程。这意味着 UI 可以立即响应用户输入,即使它处于大型渲染任务的中间,从而创造流畅的用户体验。

另一个例子是可重复使用状态。React并发可以从屏幕上删除 部分UI,然后在重复使用之前的状态时将它们添加回来。例如,当用户从一个屏幕上移开并返回时,React 应该能够将前一个屏幕恢复到与之前相同的状态。在即将到来的次要版本中,我们计划添加一个名为 <Offscreen> 的新组件来实现此模式。同样,您将能够使用 Offscreen 在后台准备新的 UI,以便在用户显示之前准备好。

并发渲染是 React 中一个强大的新工具,我们的大多数新功能都是基于它而构建的,包括 Suspense、Transitions和流式服务器渲染。但是 React 18 只是我们在这个新基础上构建的目标的开始。


逐步过渡到并发特性

从技术上讲,并发渲染是一个突破性的变化。因为并发渲染是可中断的,所以启用它时组件的行为会略有不同。

在我们的测试中,我们已经将数千个组件升级到了 React 18。我们发现几乎所有现有组件都“可以正常工作”并发渲染,没有任何更改。但是其中一些可能需要一些额外的迁移工作。尽管更改通常很小,但您仍然可以按照自己的节奏进行更改。React 18 中的新渲染行为仅仅适用于应用程序中使用新功能那部分。

整体升级策略是让您的应用程序在 React 18 上运行而不破坏现有代码。然后,您可以按照自己的节奏逐渐开始添加并发功能。您可以使用 <StrictMode> 在开发过程中帮助发现与并发相关的错误。严格模式不会影响生产环境,但在开发过程中它会记录额外的警告和预期是幂等的双重调用函数。它不会捕获所有内容,但它可以有效地防止最常见的错误类型。

升级到 React 18 后,您将能够立即开始使用并发功能。例如您可以使用 startTransition 在屏幕之间导航,而不会阻止用户输入。或者使用DeferredValue 来限制昂贵的重新渲染。

但是,从长远来看,我们希望您向应用程序添加并发的主要方式是使用支持并发的库或框架。在大多数情况下,您不会直接与并发 API 交互。例如,开发人员不会在导航到新屏幕时调用 startTransition,而是路由器库会自动将导航包装在 startTransition中。

库升级到并发兼容可能需要一些时间。我们提供了新的 API,使库更容易利用并发特性。同时,在我们努力逐步迁移 React 生态系统的过程中,请对维护者保持耐心。


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的新功能

1.自动批处理

批处理是 React 将多个状态更新分组到一个重新渲染中以获得更好的性能。如果没有自动批处理,我们只能在 React 事件处理程序中批处理更新。默认情况下,Promise、setTimeout、native event或任何其他事件内部的更新不会在 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);
2.过渡

过渡是 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:一个开始转换的钩子,包括一个跟踪挂起状态的值。
  • startTransition:当钩子无法使用时启动转换的方法。

过渡将选择并发渲染,这允许更新被中断。如果内容重新挂起,过渡也会告诉 React 继续显示当前内容,同时在后台渲染过渡内容。

3.新Suspense特性

如果组件树的一部分尚未准备好显示,Suspense 允许您以声明方式指定其加载状态:

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

Suspense 使“UI 加载状态”成为 React 编程模型中一流的声明性概念。这让我们可以在它之上构建更高级别的功能。

几年前,我们推出了 Suspense 的限量版。但是,唯一受支持的用例是使用 React.lazy 进行代码拆分,并且在服务器上渲染时根本不支持。

在 React 18 中,我们在服务器上添加了对 Suspense 的支持,并使用并发渲染特性扩展了它的功能。 React 18 中的 Suspense 与transition API 结合使用时效果最佳。如果你在过渡期间挂起,React 将防止已经可见的内容被替换为后备内容。相反,React 会延迟渲染,直到加载了足够的数据以防止出现错误的加载状态。

4.新的客户端和服务器端渲染API

在此版本中,我们借此机会重新设计了我们为在客户端和服务器上呈现而公开的 API。这些更改允许用户在升级到 React 18 中的新 API 时继续使用 React 17 模式下的旧 API。

客户端React DOM

这些新的 API 现在可以从 react-dom/client 导出:

  • createRoot:创建要渲染或卸载的根的新方法。使用它来代替 ReactDOM.render。没有它,React 18 中的新功能就无法工作。
  • hydraRoot:为服务器渲染的应用程序加水的新方法。将它与新的 React DOM 服务器 API 结合使用,而不是 ReactDOM.hydrate。没有它,React 18 中的新功能就无法工作。

createRoot 和 hydraRoot 都接受一个名为 onRecoverableError 的新选项,以防止您希望在 React 从渲染期间的错误中恢复或日志记录的水合时收到通知。默认情况下,React 将在旧版浏览器中使用 reportError 或 console.error。

服务器端React DOM

这些新的 API 现在可以从 react-dom/server 导出,并且完全支持在服务器上流式传输 Suspense:

  • renderToPipeableStream:用于 Node 环境中的流式传输。
  • renderToReadableStream:适用于现代边缘运行时环境,例如 Deno 和 Cloudflare worker。

现有的 renderToString 方法继续工作,但不鼓励。

5.新的严格模式行为

将来,我们希望添加一个功能,允许 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.
6.新钩子

useId

useId 是一个新的钩子,用于在客户端和服务器上生成唯一 ID,同时避免前后端不匹配。它主要用于组件库与需要唯一 ID 的可访问性 API的集成。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为新的流服务器渲染器需要交付无序 HTML。

function NameFields() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id + '-firstName'}>First Name</label>
      <div>
        <input id={id + '-firstName'} type="text" />
      </div>
      <label htmlFor={id + '-lastName'}>Last Name</label>
      <div>
        <input id={id + '-lastName'} type="text" />
      </div>
    </div>
  );
}

useTransition

useTransition 和 startTransition 让您将一些状态更新标记为不紧急。默认情况下,其他状态被更新为是紧急的。 React 将允许紧急状态更新(例如,更新文本输入)以中断非紧急状态更新(例如,呈现搜索结果列表)。

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);
  
  function handleClick() {
    startTransition(() => {
      setCount(c => c + 1);
    })
  }

  return (
    <div>
      {isPending && <Spinner />}
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}

useDeferredValue

useDeferredValue 允许您推迟重新渲染树的非紧急部分。它类似于去抖动,但与之相比有一些优点。没有固定的时间延迟,因此 React 将在第一次渲染反映在屏幕上后立即尝试延迟渲染。延迟渲染是可中断的,不会阻塞用户输入。

function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // Memoizing tells React to only re-render when deferredQuery changes,
  // not when query changes.
  const suggestions = useMemo(() =>
    <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">
        {suggestions}
      </Suspense>
    </>
  );
}

useSyncExternalStore

useSyncExternalStore是一个新的钩子,它允许外部存储通过强制对存储的更新同步来支持并发读取。它在实现对外部数据源的订阅时消除了对 useEffect 的需要,并且推荐用于与 React 外部状态集成的任何库。

// The most basic example simply subscribes to the entire store
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);

// you can also subscribe to a specific field
const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
);

// When server rendering, you must serialize the store value used on the
// server, and provide it to useSyncExternalStore. React will use this
// snapshot during hydration to prevent server mismatches

const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
  () => INITIAL_SERVER_SNAPSHOT.selectedField,
);

useInsertionEffect

useInsertionEffect 是一个新的钩子,它允许 CSS-in-JS 库解决在渲染中注入样式的性能问题。除非您已经构建了 CSS-in-JS 库,否则我们不希望您使用它。这个钩子将在 DOM 发生变化之后运行,但在布局效果读取新布局之前。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为 React 在并发渲染期间会屈服于浏览器,使其有机会重新计算布局。

useInsertionEffect(didUpdate);