精读React hook(十):使用useDeferredValue来做状态延迟更新

上一篇文章(《精读React hook(九):使用useTransition进行非阻塞渲染》)我们学习了,在React v18的并发模式下,使用useTransition可以在密集计算的场景下让UI无阻塞。React v18还引入了一个和useTransition相似的hook,就是useDeferredValue。为什么说相似呢,因为useDeferredValue的作用是:延迟某个值的更新,使高优先级的任务可以先行完成。

开始学习useDeferredValue之前,我们先明确useTransitionuseDeferredValue的差异:

  • useTransition主要关注点是状态的过渡。它允许开发者控制某个更新的延迟更新,还提供了过渡标识,让开发者能够添加过渡反馈。
  • useDeferredValue主要关注点是单个值的延迟更新。它允许你把特定状态的更新标记为低优先级。

简而言之,如果你想提供过渡反馈,就用useTransition,如果不需要提供过渡反馈,用useDeferredValue就可以。

useDeferredValue基础使用

jsx 复制代码
const deferredValue = useDeferredValue(someValue);

其中someValue是你想要延迟的值,它可以是任何类型。

deferredValue的渲染有两种情况:

  1. 在初始渲染时deferredValue的值将与someValue的值相同。
  2. 在UI更新期间 ,因为deferredValue的优先级较低,即使并发模式下deferredValue已在后台更新,React也会先使用旧值渲染,当其它高优先级的状态更新完成,才会把deferredValue新值渲染出来。

示例分析

想象一个场景,你在大数据量场景下做查询,前端渲染就需要延迟更新列表,还希望只有最后一次查询的数据被保留,这时候useDeferredValue就派上用场了,例如:

jsx 复制代码
import { useState, useDeferredValue, memo } from 'react';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      {/* 输入框的值直接与 text 绑定,所以输入框会实时显示用户的输入 */}
      <input value={text} onChange={e => setText(e.target.value)} />
      {/* SlowList 组件接受 deferredText 作为属性,渲染优先级会被降低 */}
      {/* 在 UI 真正更新前,如果 deferredText 被更新多次,也只会保留最后一次的结果 */}
      <SlowList text={deferredText} />
    </>
  );
}

const SlowList = memo(function SlowList({ text }) {
  const arr = [];
  for (let i = 0; i < 200; i++) {
    let startTime = performance.now();
    while (performance.now() - startTime < 1) {}
    if (String(i).includes(text)) {
      arr.push(<li key={i}>{i}</li>);
    }
  }
  return (
    <ul className="items">
      {arr}
    </ul>
  );
});

如果你想对照不用useDeferredValue的效果,只要把<SlowList>修改掉就可以:

jsx 复制代码
<SlowList text={text} />

线上示例可以来<👉我的演示站>体验。

使用useDeferredValue注意事项

  • useDeferredValue仅在开启React并发模式的时候才有效

    jsx 复制代码
    // React v18以前
    ReactDOM.render(<app />, rootNode) // ❌ 无法开启useTransition
    
    // React v18
    ReactDOM.createRoot(rootNode).render(<app />) // ✅ 开启useTransition
  • 传递给useDeferredValue的值应该是原始值 (如字符串和数字)或在渲染外部创建的对象

    为什么对象需要创建在外部创建?这其实和JavaScript的机制有关,即使每次创建相同的对象,JavaScript依然会每次生成新的引用。这意味着每次你在组件的渲染函数中创建一个新的对象并将其传递给useDeferredValue,你都是在传递一个新的、与上一次渲染不同的引用。

    jsx 复制代码
    function MyComponent() {
      const obj = { name: 'John' }; // 每次 MyComponent 重新渲染时,都会创建一个新的 obj 对象,它们的引用是不同的
    
    	// useDeferredValue 通过 Object.is 来检查值是否有变化,引用不同的对象会被当作不同的值
      const deferredValue = useDeferredValue(obj);
    
      // ...其他代码...
    }
  • 当同一个useDeferredValue在渲染前接收到多次不同的值时,只有最后一个会被渲染。想象一下,前端在做大数据量查询的时候,我们当然希望只有最后一次查询的数据成功渲染。

  • useTransition一样,useDeferredValue只会中断或延迟UI的渲染,不会阻止网络请求

  • useDeferredValue<Suspense>结合使用,在useDeferredValue更新时,<Suspense>的fallback是不会出现的,页面上是继续显示useDeferredValue的旧值。这一点和useTransition不一样。

和节流、防抖的关系

从上面的学习可以看出来,useDeferredValue在某些场景下是可以替换掉节流和防抖的,我们先来分析它们各自的特性,然后看看它们分别适用什么场景?

防抖与节流的局限性

  • 这两种方法都是为了控制函数的执行频率,但它们是阻塞的,可能会导致不流畅的用户体验。

useDeferredValue的优势

  • 它与React深度集成,可以适应用户的设备。如果设备性能好,延迟的重新渲染会很快完成;如果设备性能差,重新渲染会相应地延迟。
  • 它不需要选择固定的延迟,与防抖和节流不同。
  • useDeferredValue执行的重新渲染是可中断的。这意味着在React重新渲染期间,如果发生了其他更新,React会中断当前的渲染并处理新的更新。

适用场景

  • 如果要优化的工作不是在渲染期间进行的,例如减少网络请求,那么防抖和节流仍然是有用的。
  • 如果优化的目标是和渲染有关的,建议使用useDeferredValue

结语

useDeferredValue带来的是可延迟的状态更新,当实际工作中遇到大数据量渲染的场景,不妨想想是否引入useDeferredValue

系列文章列表

精读React hook(一):useState 的几个基础用法和进阶技巧

精读React hook(二):React状态管理的强大工具------useReducer

精读React hook(三):useContext从基础应用到性能优化

精读React hook(四):useRef的多维用途

精读React hook(五):useEffect使用细节知多少?

精读React hook(六):useLayoutEffect解决了什么问题?

精读React hook(七):用useMemo来减少性能开销

精读React hook(八):我们为什么需要useCallback

精读React hook(九):使用useTransition进行非阻塞渲染

精读React hook(十):使用useDeferredValue来做状态延迟更新

未完待续......

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript