useDeferredValue的作用

news/2025/4/2 3:10:41/文章来源:https://www.cnblogs.com/jiajialove/p/18801537

前言

useDeferredValue是react18新增的一个用于优化性能的一个hook,它的作用是延迟获取一个值,实际开发中的常规用法与我们之前所用的防抖和节流很相似,但是也有一定的区别。本篇文章我们就逐步分析它的设计原理和用法,并且讨论它与防抖节流的区别和它自身的优势。在讨论useDeferredValue之前,我们要先了解react的两个知识点,嵌套组件的渲染过程和记忆组件memo原理作用。

嵌套组件的渲染过程

子组件正常渲染

提到组件嵌套我们非常熟悉,因为整个react页面都是只有一个根组件,所有组件都是这个跟组件的子组件,那我们就分析一下有子组件的时候,父组件重现渲染会发生什么。
其实当我们的父组件重新渲染的时候,我们的所有子组件也会全部重新渲染一遍,这样设计主要是为了保持组件树的一致性和子组件数据更新的及时性。
例如一些子组件与父组件存在数据传递的情况,如果子组件不重新渲染,那么就无法得到最新的父组件传递过去的数据,也就无法及时更新页面。下面我们使用一个小案例测试这一场景:

 
tsx
代码解读
复制代码
import React from 'react'

function Test01(props: {count: number}) {
    const { count } = props

    console.log('Test01 render')

    return (
        <div>
            <p>This is Test01 Page {count}</p>
        </div>
    )
}

function Test02() {

    console.log('Test02 render')

    return (
        <div>
            <p>This is Test02 Page</p>
        </div>
    )
}

function Demo01() {

    console.log('Demo01 render')

    const [count, setCount] = React.useState(0)

    return (
        <div>
            <p>This is Demo01 Page</p>
            <button onClick={() => { setCount(count + 1) }}>AddButton</button>
            <Test01 count={count} />
            <Test02 />
        </div>
    )
}

export default Demo01

根据测试,我们发现当我们点击AddButton时,Test01组件的count值在持续增加,并且控制台也会依次打印出如下内容,说明我们的子组件也根据使用顺序依次渲染,并且子组件得到了父组件传入的最新值。
image.png

子组件渲染缓慢

在上面这种场景下,假如我们的其中一个子组件渲染遇到了大量计算,渲染很慢,会发生什么呢,我们稍微修改一下代码,我们把Test01组件中加入一个两亿次的循环,模拟大量计算导致的渲染变慢,同时我们在Demo01组件中加入另一个状态number并将其传入Test02组件,当分别点击AddButton和AddNumber时,测试页面和控制台打印情况:

 
tsx
代码解读
复制代码
import React from 'react'

function Test01(props: {count: number}) {
    const { count } = props

    console.log('Test01 render')

    let k = 0
    for (let i = 0; i <= 200000000; i += 1) {
        k = i
    }

    return (
        <div>
            <p>{k}This is Test01 Page Count {count}</p>
        </div>
    )
}

function Test02(props: {number: number}) {
    const { number } = props

    console.log('Test02 render')

    return (
        <div>
            <p>This is Test02 Page Number {number}</p>
        </div>
    )
}

function Demo01() {

    console.log('Demo01 render')

    const [count, setCount] = React.useState(0)
    const [number, setNumber] = React.useState(0)

    const handleAddCount = () => {
        console.log('handleAddCount')
        setCount(count + 1)
    }

    const handleAddNumber = () => {
        console.log('handleAddNumber')
        setNumber(number + 1)
    }

    return (
        <div>
            <p>This is Demo01 Page</p>
            <button onClick={handleAddCount}>AddButton</button>
            <button onClick={handleAddNumber}>AddNumber</button>
            <Test01 count={count} />
            <Test02 number={number} />
        </div>
    )
}

export default Demo01

根据我们测试会发现,不管我们点击的是哪个按钮,页面数字显示都会卡顿,没有及时显示出来,同时控制台都会打印出来下图结果,根据结果我们可以看出,当我们修改父组件的状态时,不管修改的是哪一个,子组件都会全部渲染,而且当遇到一个渲染缓慢的子组件时,父组件和其他子组件都会等待它渲染完成才会启动下次渲染,这就导致了无论我们修改了哪个状态,我们组件都会渲染的很缓慢。
image.png
不过我们发现当我们点击AddNumber时,count的值一直保持不变,Test01的渲染结果也是一直保持不变,这个是我们react组件要求必须是纯函数的一个特性,当输入的props不发生改变的时候,返回结果永远都是一样的。既然如此那当我们点击AddNumber时,Test01组件完全没有重新渲染的必要,所以react官方为了解决这一问题,引入可记忆组件的概念,下面我们就详细分析记忆组件的作用。

记忆组件memo原理作用

react引入记忆组件,就是为了避免不必要的渲染,也就是说当我们向子组件传入的props不发生改变的时候,子组件不需要重新渲染。想要组件变成记忆组件,我们只需要把组件包裹在memo函数中就可以了,我们把上述案例使用memo进行改造,此时我们把Test01,Test02使用memo函数返回,那么这两个组件就变成了记忆组件,那么以后只有该组件的props发生改变,才会重新渲染此组件。

 
tsx
代码解读
复制代码
import React, { memo } from 'react'

const Test01 = memo((props: {count: number}) => {
    const { count } = props

    console.log('Test01 render')

    let k = 0
    for (let i = 0; i <= 200000000; i += 1) {
        k = i
    }

    return (
        <div>
            <p>{k}This is Test01 Page Count {count}</p>
        </div>
    )
})

Test01.displayName = 'Test01'

const Test02 = memo((props: {number: number}) => {
    const { number } = props

    console.log('Test02 render')

    return (
        <div>
            <p>This is Test02 Page Number {number}</p>
        </div>
    )
})

Test02.displayName = 'Test02'

function Demo01() {

    console.log('Demo01 render')

    const [count, setCount] = React.useState(0)
    const [number, setNumber] = React.useState(0)

    const handleAddCount = () => {
        console.log('handleAddCount')
        setCount(count + 1)
    }

    const handleAddNumber = () => {
        console.log('handleAddNumber')
        setNumber(number + 1)
    }

    return (
        <div>
            <p>This is Demo01 Page</p>
            <button onClick={handleAddCount}>AddButton</button>
            <button onClick={handleAddNumber}>AddNumber</button>
            <Test01 count={count} />
            <Test02 number={number} />
        </div>
    )
}

export default Demo01

我们使用改造后的代码进行测试,此时我们快速点击AddNumber按钮,我们发现当我们快速点击AddNumber时,并不会像之前那样有卡顿的现象变得非常丝滑,并且我们会在控制台看到如下结果,这也说明当我们点击AddNumber时,count没有发生改变,Test01组件也没有重新渲染,这样就起到了避免渲染无关组件带来的额外开销,也不会因为一个组件的渲染缓慢导致整个渲染的缓慢,对项目性能可以有个很好的优化。不过当我们点击AddButton时依然会有卡顿,这是不可避免的,所以代码中一定避免这样的大量循环。
image.png

useDeferredValue详解

我们搞懂上面两个概念之后,我们下面就正式开始逐步分析useDeferredValue的原理和使用方法,首先我们需要对useDeferredValue进行一个简单的介绍。

了解useDeferredValue

useDeferredValue是react18引入的一个用于性能优化的hooks,它用于延迟获取某个值,并且在延迟获取之间将会返回旧的值。
单从官方定义我们难以理解它的实际含义和作用,这里我来翻译一下,官方表达的意思就是使用useDeferredValue传入一个参数,这个参数是一个任意类型的值,例如我们就传入一个使用useState定义的变量value,value的初始值是字符串'abc',当我们修改value时,他就会延迟返回一个最新的value值,例如下面代码

 
tsx
代码解读
复制代码
const [value, setValue] = useState('abc')

const deferredValue = useDeferredValue(value)

此时我们修改value值为'abcd'那么接下来会发生什么呢,首先由于value的改变,当前组件会被重新渲染,而这次渲染useDeferredValue(value)会返回之前的值,也就是'abc',然后后台会安排一次重新渲染,此时useDeferredValue(value)会返回最新值'abcd'。
我们直接在代码中测试,在如下代码中,我们将count值传入useDeferredValue并返回一个延迟的count,我们测试当我们点击一次AddButton时查看打印情况。

 
tsx
代码解读
复制代码
import React, { useDeferredValue } from 'react'

function Demo01() {

    console.log('Demo01 Render')

    const [count, setCount] = React.useState(0)

    const handleAddCount = () => {
        console.log('handleAddCount')
        setCount(count + 1)
    }

    const deferredCount = useDeferredValue(count)

    console.log('count: ', count)
    console.log('deferredCount: ', deferredCount)

    return (
        <div>
            <p>This is Demo01 Page</p>
            <button onClick={handleAddCount}>AddButton</button>
        </div>
    )
}

export default Demo01

当我们点击一次AddButton时,控制台会有如下打印,首先我们点击了AddButton给count设置了新的值,组件由于状态的改变进行第一次渲染,而此时deferredCount返回值是0,也就是初始传入的值,这就对应了官方所说的,首次渲染不会返回最新值,而是返回之前的旧值,也就是初始值。
紧接着有出现了一次渲染,不过这次渲染并不是我们操作的原因,而是官方所说的会在后台会安排一次重新渲染,然后在这次重新渲染中,useDeferredValue将返回上次渲染传入的最新值,而我们上次渲染传给useDeferredValue的值是增加后的数字1,因此在后台的二次渲染中就返回了最新值1.
image.png
通过上面的基本解释,我们大概了解了useDeferredValue的运行机制,然而这样的机制有什么作用呢,单独看的话甚至还额外多了一次渲染,又有什么必要呢,我们下面用一个官方的案例解释它的作用。

实现输入框内容实时更新到列表功能

我们实现一个功能,当我们在输入框中内容时,将内容实时显示在下面的列表中,我们在列表中故意加入了一个大量的循环,来模拟列表存在大量计算,渲染缓慢的场景。

不使用useDeferredValue实现

Test组件

 
tsx
代码解读
复制代码
import React, { useState } from 'react'
import List from './list'

function Test() {
    const [inputValue, setInputValue] = useState('')

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        console.log('handleChange')
        setInputValue(e.target.value)
    }

    return (
        <div>
            <input
                type="text"
                value={inputValue}
                onChange={handleChange}
                placeholder="Search..."
            />
            <List inputValue={inputValue} />
        </div>
    )
}

export default Test

List组件

 
tsx
代码解读
复制代码
import React, { memo } from 'react'

// 定义一个列表组件List
function List(props: { inputValue: string }) {
    const { inputValue } = props

    console.log('List render')

    let k = 0
    for (let i = 0; i <= 200000000; i += 1) {
        k = i
    }

    return (
        <ul>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
        </ul>
    )
}

export default memo(List)

我们这里就是很简单的,输入框输入内容,我们修改inputValue为最新值,并把inputValue传入list组件内进行显示,只不过list组件有个模拟渲染缓慢的循环。我们把list组件使用memo返回,使其变成一个记忆组件,只是现在每次props都会改变,暂时起不到作用。我们根据上述代码开始测试,我们在输入框快速输入字符,查看页面表现和控制台打印情况
wcce1-m4ua3.gif
根据页面表现我们可以看出,当我们快速输入时,页面有着明显卡顿,这是因为list组件的渲染非常缓慢,然而我们每次输入都会修改list组件的props值,也就意味着每次输入都会使list重新渲染,而react的渲染机制又是不可中断的,所以就会出现排队渲染的情况,只有等list组件上次渲染结束,test组件才能开始下次渲染,才能将最新的值显示在输入框内,这样会给用户很不好的操作体验。
image.png
根据控制台打印结果我们可以看出,每次渲染大概需要0.2秒的时间,记住这个时间,后续还会用到。
接下来我们使用useDeferredValue优化代码,再进行测试。

使用useDeferredValue实现

我们将Test组件做以下改造,将inputValue传入useDeferredValue,并返回一个deferredValue,然后我们将deferredValue传入List组件,并在List组件中打印出deferredValue的值
Test组件

 
tsx
代码解读
复制代码
import React, { useState, useDeferredValue } from 'react'
import List from './list'

function Test() {
    const [inputValue, setInputValue] = useState('')

    const deferredValue = useDeferredValue(inputValue)

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        console.log('handleChange')
        setInputValue(e.target.value)
    }

    console.log('inputValue:', inputValue)
    console.log('deferredValue:', deferredValue)

    return (
        <div>
            <input
                type="text"
                value={inputValue}
                onChange={handleChange}
                placeholder="Search..."
            />
            <List inputValue={deferredValue} />
        </div>
    )
}

export default Test

List组件

 
tsx
代码解读
复制代码
import React, { memo } from 'react'

// 定义一个列表组件List
function List(props: { inputValue: string }) {
    const { inputValue } = props

    console.log('List render: ', inputValue)

    let k = 0
    for (let i = 0; i <= 300000000; i += 1) {
        k = i
    }

    return (
        <ul>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
            <li>Cycle Times {k}Text: {inputValue}</li>
        </ul>
    )
}

export default memo(List)

然后我们直接在输入框快速输入字符进行测试,观察页面表现
5m999-r5tlb.gif
从页面表现我们可以看出,输入框显示最新输入内容变得很丝滑,不会那么卡顿,这是什么原因呢,我们通过过滤控制台数据逐个分析
过滤deferredValue
image.png
过滤List render
image.png
分析打印结果:
我们从打印结果可以看出,并不是每次输入新的内容时,deferredValue都会返回新的值,而是会隔一段时间返回一次,而list组件的渲染次数也刚好是deferredValue返回新的值的次数。这就充分解释了useDeferredValue作用就是不会立刻返回新的结果,会等到上一次返回的新结果处理完才会继续返回,我们还记得上面测试的按照List组件的渲染时间大概就是0.2秒,也就是说List组件所依赖的这个延迟返回的值,会等到list组件渲染完成后才会再返回新的结果。
而在useDeferredValue两次返回新结果之间,并不会影响父组件也就是Test组件的渲染,这样就避免了输入框内容不能快速展现的问题,也避免了多次重复渲染List组件产生的额外消耗。

useDeferredValue与防抖节流对比

我们从上面的结果和表现可以看出,useDeferredValue hook的作用非常类似我们之前做的防抖节流函数,那他们之前的区别,官方解释的很好,我这里就直接照搬过来。
防抖:是指在用户停止输入一段时间(例如一秒钟)之后再更新列表。
节流:是指每隔一段时间(例如最多每秒一次)更新列表。
虽然这些技术在某些情况下是有用的,但 useDeferredValue 更适合优化渲染,因为它与 React 自身深度集成,并且能够适应用户的设备。
与防抖或节流不同,useDeferredValue 不需要选择任何固定延迟时间。如果用户的设备很快(比如性能强劲的笔记本电脑),延迟的重渲染几乎会立即发生并且不会被察觉。如果用户的设备较慢,那么列表会相应地“滞后”于输入,滞后的程度与设备的速度有关。
此外,与防抖或节流不同,useDeferredValue 执行的延迟重新渲染默认是可中断的。这意味着,如果 React 正在重新渲染一个大型列表,但用户进行了另一次键盘输入,React 会放弃该重新渲染,先处理键盘输入,然后再次开始在后台渲染。相比之下,防抖和节流仍会产生不顺畅的体验,因为它们是阻*的:它们仅仅是将渲染阻塞键盘输入的时刻推迟了。
如果你要优化的工作不是在渲染期间发生的,那么防抖和节流仍然非常有用。例如,它们可以让你减少网络请求的次数。你也可以同时使用这些技术。


作者:Harbour
链接:https://juejin.cn/post/7260326520336760893
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/908485.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring AI 增加混元 embedding 向量功能

上次我们讨论了如何将自己的开源项目发布到 Maven 中央仓库,确保其能够方便地被其他开发者使用和集成。而我们的项目 spring-ai-hunyuan 已经具备了正常的聊天对话功能,包括文本聊天和图片理解等基础功能。今天,我们进一步优化和扩展了该项目,新增了一个向量化功能。如图所…

如何选择合适的数据同步软件,提升企业业务效率和数据管理能力?

数据同步软件对企业提升决策效率、优化客户体验、保障运营稳定性等诸多方面都有显著好处,可以实时洞察业务状况,及时发现问题与机会,提升风险控制能力,保障数据一致性,优化资源配置,促进团队协作。一、应用场景 通常金融、电商、医疗、制造等行业的企业会有数据同步的需求…

2. RabbitMQ 的详细安装步骤(两种方式,第一种:yum 安装;第二种:docker 容器安装)

2. RabbitMQ 的详细安装步骤(两种方式,第一种:yum 安装;第二种:docker 容器安装) @目录2. RabbitMQ 的详细安装步骤(两种方式,第一种:yum 安装;第二种:docker 容器安装)1. 第一种方式:yum 安装 RabbitMQ 的详细步骤:1.1 安装 RabbitMQ web 管理插件1.2 在 RabbitMQ …

20244221李留斌《python程序设计》实验报告

20244104 2024-2025-2 《Python程序设计》实验x报告 课程:《Python程序设计》 班级:2442 姓名:李留斌 学号:20244221 实验教师:王志强 实验日期:2025年3月23日 必修/选修: 公选课 一、实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能; 3.编写程序,练习…

【分享】Ftrans内外网文件摆渡系统:让数据传输更安全更可靠!

随着大数据时代的到来,数据的重要性日渐得到重视,数据作为数字经济时代下的基础性资源和战略性资源,是决定国家经济发展水平和竞争力的核心驱动力。以行业为维度来看,数据泄露已发生在并影响了各个行业,全球范围内,各行业发生数据泄露的数量和损失都在增加。很多企业为了…

地球无法承受 AI,是时候踩刹车了

作者:Kollibri terre Sonnenblume公有领域艺术作品,作者提供,来自公共领域元素。**前言: **如果你不想阅读完整篇,这里是本篇的作者的核心观点:人工智能(AI)虽然在技术上有巨大的潜力,但它对环境的负面影响极其严重,可能加剧当前面临的多重危机,如气候变化、资源枯竭…

VMware ESXi 8.0U3d macOS Unlocker OEM BIOS 集成驱动版,新增 12 款 I219 网卡驱动

VMware ESXi 8.0U3d macOS Unlocker & OEM BIOS 集成驱动版,新增 12 款 I219 网卡驱动VMware ESXi 8.0U3d macOS Unlocker & OEM BIOS 集成驱动版,新增 12 款 I219 网卡驱动 VMware ESXi 8.0U3d macOS Unlocker & OEM BIOS 集成网卡驱动和 NVMe 驱动 (集成驱动版…

Gitea Enterprise 23.6.0 (Linux, macOS, Windows) - 本地部署的企业级 Gti 服务

Gitea Enterprise 23.6.0 (Linux, macOS, Windows) - 本地部署的企业级 Gti 服务Gitea Enterprise 23.6.0 (Linux, macOS, Windows) - 本地部署的企业级 Gti 服务 The Premier Enterprise Solution for Self-Hosted Git Service 请访问原文链接:https://sysin.org/blog/gitea/…

Autodesk Maya 2026 Multilanguage (macOS, Windows) - 三维动画和视觉特效软件

Autodesk Maya 2026 Multilanguage (macOS, Windows) - 三维动画和视觉特效软件Autodesk Maya 2026 Multilanguage (macOS, Windows) - 三维动画和视觉特效软件 三维计算机动画、建模、仿真和渲染软件 请访问原文链接:https://sysin.org/blog/autodesk-maya/ 查看最新版。原创…

Autodesk AutoCAD 2026 (macOS, Windows) - 自动计算机辅助设计软件

Autodesk AutoCAD 2026 (macOS, Windows) - 自动计算机辅助设计软件Autodesk AutoCAD 2026 (macOS, Windows) - 自动计算机辅助设计软件 计算机辅助设计 (CAD) 软件 请访问原文链接:https://sysin.org/blog/autodesk-autocad/ 查看最新版。原创作品,转载请保留出处。 作者主页…

VMware Aria Operations for Logs 8.18.3 新增功能简介

VMware Aria Operations for Logs 8.18.3 新增功能简介VMware Aria Operations for Logs 8.18.3 - 集中式日志管理 请访问原文链接:https://sysin.org/blog/vmware-aria-operations-for-logs/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org集中式日志管理 VMwa…

在 VS Code 中,一键安装 MCP Server!

大家好!我是韩老师。 本文是 MCP 系列文章的第三篇。之前的两篇文章是: Code Runner MCP Server,来了! 从零开始开发一个 MCP Server!经过之前两篇文章的介绍,相信不少童鞋已经用上甚至开发起了第一个 MCP Server。 不过呢,还是遇到一些童鞋在安装/配置 MCP Server 的时…