React:useTransition() vs useDeferredValue()

206 阅读3分钟

原文链接:React: useTransition() vs useDeferredValue(),2022.03.28,by Maximilian Schwarzmüller

React 18 和并发模式

React 18 引入了一个重要的新概念:并发(Concurrency)。并发就是能同时处理多个状态更新,或者更准确地说,是关于优先处理某些状态更新而不是其他状态更新,来保证用户界面始终是有响应性的。

useTransition()useDeferredValue()

我创建了一个视频教程,其中解释了并发在什么场景下会很重要。在视频中,我还展示了如何使用 useTransition()useDeferredValue() 来向 React 提供有关状态更新优先级的提示,以及这两个 Hook 之间差异,我还写了一个小 demo

以下是非常简短的总结:

使用 useTransition()

useTransition() 可以用来告诉 React 某些状态更新具有较低的优先级(即,所有其他状态更新或 UI 渲染触发具有更高的优先级)。

调用 useTransition() 时,你会得到包含 2 个成员的数组:一个 isPending,是个布尔值,告知当前低优先级状态更新是否还处在挂起状态;还有一个 startTransition() 函数,用他来包装一个状态更新,告诉 React 它属于低优先级更新。

下面是如何使用 useTransition() Hook 的例子:

function App() {
  const [isPending, startTransition] = useTransition();
  const [filterTerm, setFilterTerm] = useState('');

  const filteredProducts = filterProducts(filterTerm);

  function updateFilterHandler(event) {
    startTransition(() => {
      setFilterTerm(event.target.value);
    });
  }

  return (
    <div id="app">
      <input type="text" onChange={updateFilterHandler} />
      {isPending && <p>Updating List...</p>}
      <ProductList products={filteredProducts} />
    </div>
  );
}

setFilterTerm() 状态更新函数被 startTransition() 包装,因此 React 以较低的优先级处理这个状态更新。在例子中,这表示输入内容会保持响应并能立即对按键作出反应。如果不使用 useTransition(),则应用程序可能会变得没有响应性,特别是在较慢的设备上。

使用 useDeferredValue()

useTransition() 能让你完全掌控,决定包装哪些更新状态代码并作为“低优先级”处理。但有时候,你可能无法访问实际的状态更新代码(比如,它可能是由某个第三方库执行的),或者出于某种原因,你不能使用 useTransition()

在这种情况下,你可以使用 useDeferredValue()

使用 useDeferredValue() 时,不是将状态更新代码包装起来,而是将最终生成或修改后的值进行包装(无论是状态值本身,还是基于状态值计算出来的值)。

我们看一个基础的例子:

function ProductList({ products }) {
  const deferredProducts = useDeferredValue(products);
  return (
    <ul>
      {deferredProducts.map((product) => (
        <li>{product}</li>
      ))}
    </ul>
  );
}

这里使用 useDeferredValue() 来包装 products。因此,React 将优先处理 products 之外的其他状态或 UI 更新。

使用哪个比较合适?

如上所述,区别在于 useTransition() 用来包装状态更新代码,而 useDeferredValue() 则用来包装受状态更新影响的值。你不需要(也不应该)两个都用,因为它们最终实现的目标是一样的。

如果你有一些状态更新想要以较低优先级处理并且能够访问状态更新代码, 那么可以选择使用 useTransition()。如果你无法访问状态更新代码,则应该使用 useDeferredValue()

不要过度使用!

重要提示:你不应该开始使用 useTransition() 包装所有状态更新或使用 useDeferredValue() 包装所有值。这些 Hook 比较适合在无法通过其他方式进行优化、而又比较复杂的用户界面或组件中使用。你应始终记住其他的性能改进方式,如延迟加载(lazy loading)、分页(pagination) 、使用工作线程(worker threads)或让后端来承担部分计算任务。