React 18——[译文]新功能:startTransition

892 阅读7分钟

新功能:startTransition

概述

在React 18中,我们引入了一个新的API,即使你你的应用在大屏幕更新,也能保持更新。 这个新的API让你通过将特定的更新标记为 "transitions "来大幅改善用户互动。React将让你在状态转换时提供视觉反馈,并在转换发生时保持浏览器的响应。

Real world example: adding startTransition for slow renders

解决了什么

构建感觉流畅、响应迅速的应用程序并不总是容易的。有时,像点击一个按钮或在一个输入框中打字这样的小动作会导致屏幕上发生很多事情。这可能导致页面冻结或挂起,而所有的工作正在进行。

例如,考虑输入一个过滤数据列表的输入字段。你需要在状态中存储该字段的值,以便你能过滤数据并控制该输入字段的值。你的代码可能看起来像这样:

// Update the input value and search results
setSearchQuery(input);

在这里,每当用户输入一个字符,我们就更新输入值,并使用新的值来搜索列表并显示结果。对于大屏幕的更新,这可能会造成页面上的滞后,而所有的东西都会被渲染,使打字或其他互动感到缓慢和没有反应。即使列表不是太长,列表项本身也可能很复杂,每次按键都不同,可能没有明确的方法来优化它们的渲染。

从概念上讲,问题在于有两个不同的更新需要发生。第一个更新是一个紧急的更新,以改变输入字段的值,并有可能改变它周围的一些用户界面。第二个是不太紧急的更新,以显示搜索的结果。

// Urgent: Show what was typed
setInputValue(input);

// Not urgent: Show the results
setSearchQuery(input);

用户期望第一次更新是立即的,因为本地浏览器对这些互动的处理是快速的。但第二次更新可能会有点延迟。用户并不期望它立即完成,这很好,因为可能有很多工作要做。(事实上,开发者经常用debouncing等技术人为地延迟这种更新)。

在React 18之前,所有的更新都是紧急渲染的。这意味着上面的两个状态仍然会在同一时间被渲染,并且仍然会阻止用户看到他们互动的反馈,直到一切都渲染完毕。我们缺少的是一种告诉React哪些更新是紧急的,哪些不是。

startTransition做了什么

这个新的 startTransition API 赋予你了可以标记为 "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包裹的更新被当作非紧急事件来处理,如果有更紧急的更新,如点击或按键,则会被打断。如果一个过渡被用户打断(例如,连续输入多个字符),React会扔掉未完成的陈旧的渲染工作,只渲染最新的更新。

过渡让你保持大多数交互的敏捷性,即使它们导致了重大的UI变化。它们还可以让你避免浪费时间去渲染那些不再相关的内容。

startTransition是什么

我们把状态更新分为两类。

  • 紧急更新反映了直接的互动,如打字、点击、按压等等。
  • 过渡性更新将用户界面从一个视图过渡到另一个。

像打字、点击或按压这样的紧急更新,需要立即做出反应,以符合我们对物理物体行为方式的直觉。否则他们就会感到 "不对劲"。然而,转换是不同的,因为用户并不期望在屏幕上看到每个中间值。

例如,当你在一个下拉菜单中选择一个过滤器时,你希望过滤器按钮本身在你点击时能立即做出反应。然而,实际结果可能会单独过渡。一个小的延迟将是难以察觉的,而且往往是预期的。而且,如果你在结果渲染完成之前再次改变过滤器,你只关心看到最新的结果。

在一个典型的React应用中,大多数更新在概念上是过渡更新。但出于向后兼容的原因,过渡是选择进入的。默认情况下,React 18仍然将更新作为紧急处理,你可以通过将更新包装成startTransition来标记为过渡。

startTransition与setTimeout的不同

一个常用的解决上述问题的方法是外面包一层setTimeout定时器

// Show what you typed
setInputValue(input);

// Show the results
setTimeout(() => {
  setSearchQuery(input);
}, 0);

这将使第二次更新推迟到第一次更新呈现之后。Throttling和debouncing是这种技术的常见变种。

一个重要的区别是,startTransition不像setTimeout那样被安排在稍后的时间。它是立即执行的。传递给startTransition的函数是同步运行的,但其中的任何更新都被标记为 "过渡"。React将在以后处理更新时使用这些信息来决定如何渲染更新。这意味着我们开始渲染更新的时间比用超时包装的要早。在一个快速的设备上,两个更新之间会有很小的延迟。在慢速设备上,延迟会更大,但用户界面会保持响应。

另一个重要的区别是,在setTimeout内的大屏幕更新仍然会锁定页面,只是在超时后。如果用户在超时发生时仍在打字或与页面互动,他们仍将被阻止与页面互动。但是标有startTransition的状态更新是可以中断的,所以它们不会锁住页面。它们让浏览器在渲染不同组件之间的小空隙中处理事件。如果用户的输入发生了变化,React就不必继续渲染用户不再感兴趣的东西了。

最后,因为setTimeout只是简单地延迟更新,显示加载指示器需要编写异步代码,这通常是很脆的。有了过渡,React可以为你跟踪待定状态,根据过渡的当前状态进行更新,并让你能够在用户等待时显示加载反馈。

当处理transition 时,应该做什么

作为一个最佳实践,你会希望通知用户在后台有工作发生。为此,我们提供了一个带有isPending标志的Hook,用于过渡期。

import { useTransition } from 'react';


const [isPending, startTransition] = useTransition();

isPending的值在transition 为true,允许你在用户等待时显示一个加载提示。

{isPending && <Spinner />}

你包裹在transition中的状态更新不一定要在同一个组件中产生。例如,搜索输入中的旋转器可以反映重新渲染搜索结果的进度。

为什么不写更快的代码

编写更快的代码和避免不必要的重现仍是优化性能的好方法。转场是对这一点的补充。它们可以让你在重大的视觉变化中保持UI的响应性--例如,在显示一个新屏幕时。这很难用现有的策略来优化。即使在没有不必要的重新渲染的情况下,过渡仍然提供了比把每一次更新都视为紧急的更好的用户体验。

什么时候去使用startTransition

你可以使用startTransition来包装任何你想转移到后台的更新。通常情况下,这些类型的更新分为两类。

  • 缓慢的渲染。这些更新需要时间,因为React需要执行大量的工作,以便过渡到UI来显示结果。下面是一个真实世界的演示,添加startTransition以在昂贵的重新渲染过程中保持应用的响应性。
  • 网络速度慢。这些更新需要时间,因为React正在等待来自网络的一些数据。这个用例与Suspense紧密结合。 我们很快会再次发帖,用具体的例子介绍这些用例中的每一个。如果你有任何问题,请告诉我们!

原文

New feature: startTransition