react18之startTransition

概述

在React 18中,我们引入了一个新的API,帮助在应用程序即使在视图大量更新期间也能保持响应。这个新的API通过将特定的更新标记为“transition”,让您可以极大地提升用户交互体验。React将允许您在状态变更期间提供可视化反馈,并在状态变更发生时保持浏览器响应。

它能解决什么样的问题?

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

从概念上讲,问题在于需要进行两种不同的更新。

  • 第一个更新是紧急更新,去更改输入字段的值,可能还会更改它周围的一些UI;
  • 第二个显示搜索结果是不那么紧急的更新;
  • 每次输入时,第二次更新都会在之后触发

第二次更新可能会有点延迟。用户并不需要它立即完成,这很好,因为它可能有很多工作要做。(事实上,开发人员经常使用比如“防抖”等技术人为地延迟这类更新。)
在React18之前,所有更新都是紧急的。这意味着上述两种状态会同时更新,并且直到所有内容都呈现出来前会阻碍用户看到交互的反馈。我们需要一种方法,告诉React哪些是紧急,哪些不是紧急的更新。

startTransition如何提供帮助?

import { startTransition } from 'react';        // Class 组件和 Hooks 组件

// 紧急的: 展示输入
setInputValue(input);

// 将内部的任何状态更新都标记为转换
startTransition(() => {
  // Transition: 展示结果
  setSearchQuery(input);
});

在startTransition中包装的更新被作为非紧急的来处理,并且当更紧急的更新(如单击或按键)传入时将被中断。如果一个transition被用户打断(例如,在一行中键入多个字符),React会抛弃未完成的旧更新任务,并只渲染最新的更新。
Transitions可以保持大多数的交互的快速更新,即使它们导致规模巨大的UI更改。它们还可以让您避免在渲染不相关的内容上浪费时间。

什么是Transition?

状态更新分为两类:

  • 紧急更新反映直接的交互,比如打字、单击、按下等等。
  • Transition更新将UI从一个视图转换到另一个视图。

紧急更新,如键入、过滤下拉、单击或按下,需要立即响应,以符合我们对物理动作的直观感受。否则他们会觉得“出错了”。然而,使用transitions却得到另一种感受,因为用户不希望看到屏幕上的每一个中间状态呈现的效果。轻微的延迟是难以察觉的,而且往往是所期望的。如果在效果呈现之前再次更改过滤器,则只关心看到最新的效果。
在典型的React应用中,大多数更新都是概念意义上的transition更新,(出于向后兼容性的原因) transitions是可选的。默认情况下,React 18仍然将更新处理为紧急的,并且您可以通过将更新包装到startTransition中将其标记为“transition”。

它与setTimeout有什么不同?

  • SetTimeout被安排在稍后,而startTransition立即执行。传递给startTransition的函数是同步运行的,但是其中的任何更新都被标记为“transitions”。React将在稍后处理更新时使用这些信息来决定如何呈现更新。这意味着我们开始渲染更新的时间要比包装在timeout中的更新要早。在高性能设备上,两次更新之间的延迟非常小。在较慢的设备上,延迟会更大,但UI会保持响应。
  • 另一个重要的区别是setTimeout内部的大屏幕更新仍然会锁定页面,就在超时之后。但是标记为startTransition的状态更新是可中断的,所以它们不会锁定页面。它们允许浏览器在呈现不同组件之间的小间隙中处理事件。如果用户输入发生了变化,React就会继续呈现有关最新变化的内容。
  • 最后,由于编写异步代码,通常很容易显示setTimeout的加载指示器;但是使用转换api, React可以跟踪挂起状态,根据转换的当前状态更新它,并让你能够在用户等待时显示加载反馈。

适合使用transition的场景?

使用startTransition包装任何你想要移到背后的更新。通常,这些类型的更新分为两类:

  • 渲染缓慢: 这样的更新需要时间,因为React需要执行大量的工作来转换UI以显示结果。这是一个添加了startTransition的 real-world demo,以使得应用程序在一个高开销的重新渲染中间保持响应。
  • 网络慢: 这样的更新需要时间,因为React正在等待一些来自网络的数据。

API

import { startTransition } from 'react';        // Class 组件和 Hooks 组件

// 紧急的: 展示输入
setInputValue(input);

// 将内部的任何状态更新都标记为转换
startTransition(() => {
  // Transition: 展示结果
  setSearchQuery(input);
});
// hooks用法
import { useTransition } from 'react';
const [isPending, startTransition] = useTransition();

参考:
一个现实中的例子:
https://gitlab.porsche-preview.cn/porsche-digital-china/web/opensource-projects/platform-app.git
(这个仓库拉取下来后接口不通,无法展示,请见下面的简例:)

点击进入:高开销示例

import React, {  memo } from "react";
import * as ReactDOM from "react-dom";
/*  模拟数据  */
const mockDataArray = new Array(3).fill(1);
/* 高量显示内容 */
function ShowText({ query }) {
  const text = "asdfghjk";
  let children;
  if (query !== "" && (text.indexOf(query) > 0 || text.indexOf(query) === 0)) {
    /* 找到匹配的关键词 */
    const arr = text.split(query);
    console.log(arr);
    children = (
      <div>
        {arr[0]}
        <span style={{ color: "pink" }}>{query}</span>
        {arr[1]}{" "}
      </div>
    );
  }
  return <div>{children}</div>;
}
/* 列表数据 */
function List({ query }) {
  console.log("List渲染");
  return (
    <div>
      {mockDataArray.map((item, index) => (
        <div key={index}>
          <ShowText query={query} />
        </div>
      ))}
    </div>
  );
}
/* memo 做优化处理  */
const NewList = memo(List);

function App() {
  const [value, setInputValue] = React.useState("");
  const [isTransition, setTransion] = React.useState(false);
  const [query, setSearchQuery] = React.useState("");
  const handleChange = (e) => {
    /* 高优先级任务 —— 改变搜索条件 */
    setInputValue(e.target.value);
    if (isTransition) {
      /* transition 模式 */
      React.startTransition(() => {
        /* 低优先级任务 —— 改变搜索过滤后列表状态  */
        setSearchQuery(e.target.value);
      });
    } else {
      /* 不加优化,传统模式 */
      setSearchQuery(e.target.value);
    }
  };
  return (
    <div>
      <button onClick={() => setTransion(!isTransition)}>
        {isTransition ? "transition" : "normal"}{" "}
      </button>
      <input onChange={handleChange} placeholder="输入搜索内容" value={value} />
      <NewList query={query} />
    </div>
  );
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

这个示例中,左侧按钮控制是否使用startTransition,可以很明显看到,未启用startTransition“低开销”输入卡顿,属于“高开销”的列表展示延迟明显。当启用startTransition“低开销”输入变得顺畅许多。
另外,上文提到了debounce来解决高开销的问题,那么它与startTransition有什么区别呢?
经过测试,debounce可以短效让输入变得顺畅

其他参考:知乎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容