React

Transition Hooks - useDeferredValue

useDeferredValue 是一个 React Hook,可以让你延迟更新 UI 的某些部分。
This entry is part 8 of the seriesenjoy-react-hooks

useDeferredValue 是 React 18 中引入的一个新 Hook,它允许你将某些状态的更新延迟到下一个渲染周期。这对于优化性能非常有用,特别是在处理大量数据或复杂计算时,可以避免不必要的组件重新渲染。

问题

想象一个应用程序,它配备了一个搜索框以及一个能展示大量用户信息的列表。用户可以通过在搜索框中输入用户名来筛选并查看相关用户。

下面是实现代码:

Code Playground
import { useState } from 'react';
import SlowList from './slowList';

export default function App() {
  const [searchValue, setSearchValue] = useState('')

  const handleSearch = ({target: {value}}) => {
    setSearchValue(value)     
  } 

  return (
    <div>
      <h2>User Finder Without DeferredValue </h2>

      <input 
        type="text" 
        placeholder='Search for an user...' 
        autoFocus
        value={searchValue}
        onChange={handleSearch} 
      />

      <SlowList text={searchValue} />
    </div>
  )  
}

在我们的应用程序中,用户信息是通过<SlowItem />组件来呈现的。这个组件在渲染过程中故意暂停了1毫秒,以模拟一种极度耗时的渲染场景。此外,每次进行搜索操作,都会产生500条用户信息。

那么问题就来了: 案例中的<input />输入框旨在提供即时反馈,例如当用户输入字符时,用户列表应立即更新。这意味着当用户快速输入字符时,用户列表在一秒内可能更新数十次,且用户列表的渲染是我们刻意模拟的极度耗时的渲染场景。

在没有任何优化的情况下,要求React每秒执行数十次耗时的渲染操作。在大多数设备上,浏览器是无法足够快的执行此操作,且页面必然会出现卡顿。

仔细分析下这个需求,我会发现一个核心问题:我们要保证输入框能够快速更新,而用户列表则不需要快速响应,它只需要在用户停止输入时显示正确的用户数据即可。

换句话说,在这个UI中是分为高优先级低优先级区域的:

我们希望高优先级的东西能够尽快实时更新。但是低优先级的东西应该放在次要位置。

解决方案

通过调用useDeferredValue可以获取一个值的延迟版本:

js
const deferredValue = useDeferredValue(value, initialValue?)

useDeferredValue 接收到与之前不同的值(使用 Object.is 进行比较)时,除了当前渲染(此时它仍然使用旧值),它还会安排一个可以被中断的后台重新渲染。如果 value 有新的更新,React 会从头开始重新启动后台渲染。举个例子,如果用户在输入框中的输入速度比接收延迟值的图表重新渲染的速度快,那么图表只会在用户停止输入后重新渲染。

现在让我们使用useDeferredValue来优化最开始的问题:

Code Playground
import { useState, useDeferredValue } from 'react';
import SlowList from './slowList';

export default function App() {
  const [searchValue, setSearchValue] = useState('')

  // 📌 deferredValue总是落后于searchValue, 但最终会和searchValue一致
  const deferredValue = useDeferredValue(searchValue)

  const handleSearch = ({target: {value}}) => {
    setSearchValue(value)     
  } 

  return (
    <div>
      <h2>User Finder With DeferredValue </h2>

      <input 
        type="text" 
        placeholder='Search for an user...' 
        autoFocus
        value={searchValue}
        onChange={handleSearch} 
      />

      <SlowList text={deferredValue} />
    </div>
  )  
}

优化后的实现效果有个最明显的现象,低优先级的用户列表部分只有在高优先级的输入框停止更新后才会最终更新,中间的几次后台渲染都被中断了。

用下面这张图可以解释当输入框快速输入hello时,valuedeferredValue的变化情况:

Table of Contents

Questions? Let's chat

discord logoOPEN DISCORD
6423
members online
previous article
Transition Hooks - useTransition

A TypeScript Full Stack Blog

Share articles about Typescript, React, Next.js, Node.js and Css from time to time.
No spam, unsubscribe at any time.