React

Transition Hooks - useTransition

useTransition 是一个帮助你在不阻塞 UI 的情况下更新状态的 React Hook。
This entry is part 7 of the seriesenjoy-react-hooks

问题

在深入探讨 useTransition 之前,让我们首先了解一下它旨在解决的问题。这将帮助我们更好地理解其核心价值。

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

下面是实现代码:

Code Playground
import { useState } from 'react';
import SlowItem from './slowItem';

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


  const handleSearch = ({target: {value}}) => {
    setSearchValue(value)     
    
    setFiltered(Array(500).fill(value))
  } 
  return (
    <div>
      <h2>User Finder Without Transiton</h2>

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

      <ul className='wrapper'>
        {
          filtered.map((item,index) => (
            <SlowItem key={index} text={item} />
          ))
        }
      </ul>
    </div>
  )  
}

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

上面完全实现了功能需求,但存在几点明显的性能问题:

  1. 快速输入时,输入框会卡顿和延迟
  2. 用户列表更新同样存在延迟

造成这些问题的原因是:当用户在输入框中快速输入时,每敲击一个字符都会触发一次组件的重新渲染。鉴于每次渲染都相当耗时,这就意味着每次搜索操作都可能引发大量的计算和数据处理活动。在这种情形下,用户体验可能会大受影响。用户可能会经历明显的延迟或卡顿,尤其是在他们尝试快速输入搜索关键词以便获得即时反馈时。

解决方案

正是在这样的背景下,useTransition 派上了用场。它允许开发者将这些潜在的高成本更新标记为低优先级任务,从而确保应用程序在处理这些更新时仍然对用户输入保持响应。通过这种方式

useTransition 帮助我们在不阻塞 UI 的情况下更新状态。

这有助于改善用户体验,特别是在涉及大量数据处理或复杂计算的应用程序中。

语法

让我们看一下useTransition的用法:

const [startTransition, isPending] = useTransition();

useTransition 会返回一个包含两个元素的数组:

  • isPending: 布尔值,当startTransition函数执行时返回true,执行完成返回false
  • startTransition 将回调函数作为参数的函数。此回调函数应包含与非紧急状态更新相关的代码

使用

下面我们看下使用useTransition优化后的版本, 注意三处标注📌的地方。在该版本中我们能明显感觉到性能的提升:

  1. 在快速键入时,输入框不会卡顿。
  2. 在快速键入时,因为isPending一直处于true,所以只会渲染Loading...,而不是500条用户信息。
  3. 停止输入时,isPending变为false,此时才会真正去渲染500条用户信息。
Code Playground
import { useState, useTransition } from 'react';
import SlowItem from './slowItem';

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

  // 📌 使用useTransition
  const [isPending, startTransition] = useTransition();

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

    // 📌 将setFiltered 设置为低优先级状态
    startTransition(() => {
      setFiltered(Array(500).fill(value))
    })
  } 
  return (
    <div>
      <h2>User Finder With Transiton</h2>

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

      <ul className='wrapper'>
        {
          // 📌 使用isPending来确保在startTransition执行期间加载Loading, 而不是执行耗时的渲染
          isPending ? (
            <div>Loading...</div>
          ) : ( 
            filtered.map((item,index) => (
              <SlowItem key={index} text={item} />
            ))
          )
        }
      </ul>
    </div>
  )  
}

Questions? Let's chat

discord logoOPEN DISCORD
6423
members online
previous article
State Hooks - useReducer
next article
Transition Hooks - useDeferredValue

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.