jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

React - useTransition #56

Open jtwang7 opened 1 year ago

jtwang7 commented 1 year ago

React - useTransition

转载文章:

🤓 仅供学习使用,后续会添加个人理解

在Web应用中,UI更新(比如在输入框中输入字段,从下拉框选择一个值)的优先级应该高一些,而其他操作(比如显示列表过滤内容)优先级要低一些。从React 18(2021.06发布了alpha版本)开始,我们就可以开启并发模式-concurrent,concurrent模式允许将UI更新标记为高优先级的或者可中断的低优先级操作。在这篇文章中,你会掌握如何使用useTransition()将UI更新标记为低优先级,这种操作对大量的非紧急更新非常有用。

useTransition 钩子

默认情况下,我们认为React中的所有更新都是紧急的(也就是所有更新的优先级相同)。那会导致一个问题-快速更新会被大量更新拖慢速度。然而,从React 18中新增特性-concurrency之后,你就可以将某些更新标记为可中断的和非紧急的-也就是所谓的transitions。这种新特性在大量的UI更新操作中尤其有效,比如过滤一个较大的列表。 ❇️ useTransition 这个钩子函数使得用户能够在React组件中使用concurrent模式特性: 调用 const [isPending, startTransition] = useTransitionHook() 返回一个具有两个成员的数组:

import { useTransition } from 'react';
function MyComponent() {
    const [isPending, startTransition] = useTransition();
    // ...
    const someEventHandler = (event) => {
        startTransition(() => {
            // Mark updates as transitions
            setValue(event.target.value);
        });
    }

    return <HeavyComponent value={value} />;
}

紧急的大量UI更新

接下来考虑一个所有更新都很紧急的例子,以及这些大量更新是如何影响用户体验的。 比如有一个员工名字列表和一个查找员工的姓名搜索框,这个组件会高亮显示匹配搜索内容的员工姓名。 以下是可能实现的代码:

import { useState } from 'react';
export function FilterList({ names }) {
  const [query, setQuery] = useState('');
  const changeHandler = ({ target: { value } }) => setQuery(value);

  return (
    <div>
      <input onChange={changeHandler} value={query} type="text" />
      {names.map((name, i) => (
        <ListItem key={i} name={name} highlight={query} />
      ))}
    </div>
  );
}
function ListItem({ name, highlight }) {
  const index = name.toLowerCase().indexOf(highlight.toLowerCase());
  if (index === -1) {
    return <div>{name}</div>;
  }
  return (
    <div>
      {name.slice(0, index)}
      <span className="highlight">
        {name.slice(index, index + highlight.length)}
      </span>
      {name.slice(index + highlight.length)}
    </div>
  );
}

在组件内部,query是包含查询字符串的状态变量。输入框是一个控制组件-用于在用户输入改变时更新query状态变量。当使用者在快速在输入框内键入查询字段,你可能会注意到键入延迟以及用户界面在明显的时间内没有响应。 👉 为什么会出现这种现象,如何解决呢? 在用户键入时更新输入框的值是一个必须快速执行的紧急任务,更新高亮显示匹配列表是一个繁重且不紧急的任务。大量的非紧急任务落后于轻量的紧急任务。useTransition()钩子能够帮助你区分紧急的UI更新和非紧急的UI更新。

大量的UI更新作为过渡(transitions)

之前已经提高了,你可以使用 useTransition 告诉React哪些UI更新是紧急的(比如更新输入框的值)和哪些UI更新是不紧急的transitions(比如更新高亮匹配查询内容的姓名列表) 💡 我们来对FilterList组件做一下必要的调整: 首先,我们调用 [isPending, startTransition] = useTransition() 钩子来访问startTransition()函数,然后专门这个transition创建了一个保存状态的状态变量。

import React, { useState, useTransition } from "react";

export function FilterList({ names }) {
  const [query, setQuery] = useState("");
  const [highlight, setHighlight] = useState("");

  const [isPending, startTransition] = useTransition();

  const changeHandler = ({ target: { value } }) => {
    setQuery(value);
    startTransition(() => setHighlight(value));
  };

  return (
    <div>
      <input onChange={changeHandler} value={query} type="text" />
      {isPending ? 'pending' : (
        names.map((name, i) => (
          <ListItem key={i} name={name} highlight={highlight} />
        ))
      )}
    </div>
  );
}

function ListItem({ name, highlight }) {
  const index = name.toLowerCase().indexOf(highlight.toLowerCase());
  if (index === -1) {
    return <div>{name}</div>;
  }
  return (
    <div>
      {name.slice(0, index)}
      <span className="highlight">
        {name.slice(index, index + highlight.length)}
      </span>
      {name.slice(index + highlight.length)}
    </div>
  );
}

使用了transitionos特性,如果你非常快速地在输入框中键入,你会注意到高亮列表的延迟更新。 React将紧急任务(当用户键入时更新输入框)的更新和非紧急任务(高亮显示过滤内容)的渲染区分开了,这样的操作提升了用户体验。

总结

React中的并发模式(concurrent mode)将紧急任务和非紧急任务区分开,使UI更新更加人性化。在开启React 18并发模式新特性之后,你可以使用useTransition()钩子进而使用startTransition(callback)函数。 useTransition()使你能够将默写更新标记为过渡(transitions):

const [isPending, startTransition] = useTransition();
startTransition(() => {
  // Mark updates as transitions
  setStateValue(newValue);
});