Transition Hooks - useDeferredValue
useDeferredValue
是 React 18 中引入的一个新 Hook,它允许你将某些状态的更新延迟到下一个渲染周期。这对于优化性能非常有用,特别是在处理大量数据或复杂计算时,可以避免不必要的组件重新渲染。
问题
想象一个应用程序,它配备了一个搜索框以及一个能展示大量用户信息的列表。用户可以通过在搜索框中输入用户名来筛选并查看相关用户。
下面是实现代码:
在我们的应用程序中,用户信息是通过<SlowItem />
组件来呈现的。这个组件在渲染过程中故意暂停了1毫秒
,以模拟一种极度耗时的渲染场景。此外,每次进行搜索操作,都会产生500
条用户信息。
那么问题就来了: 案例中的<input />
输入框旨在提供即时反馈,例如当用户输入字符时,用户列表应立即更新。这意味着当用户快速输入字符时,用户列表在一秒内可能更新数十次,且用户列表的渲染是我们刻意模拟的极度耗时的渲染场景。
在没有任何优化的情况下,要求React每秒执行数十次耗时的渲染操作。在大多数设备上,浏览器是无法足够快的执行此操作,且页面必然会出现卡顿。
仔细分析下这个需求,我会发现一个核心问题:我们要保证输入框能够快速更新,而用户列表则不需要快速响应,它只需要在用户停止输入时显示正确的用户数据即可。
换句话说,在这个UI中是分为高优先级和低优先级区域的:
![](/assets/series/enjoy-react-hooks/08/high-low-priority.png)
我们希望高优先级的东西能够尽快实时更新。但是低优先级的东西应该放在次要位置。
解决方案
通过调用useDeferredValue
可以获取一个值的延迟版本:
const deferredValue = useDeferredValue(value, initialValue?)
当 useDeferredValue
接收到与之前不同的值(使用 Object.is
进行比较)时,除了当前渲染(此时它仍然使用旧值),它还会安排一个可以被中断的后台重新渲染。如果 value 有新的更新,React 会从头开始重新启动后台渲染。举个例子,如果用户在输入框中的输入速度比接收延迟值的图表重新渲染的速度快,那么图表只会在用户停止输入后重新渲染。
现在让我们使用useDeferredValue
来优化最开始的问题:
优化后的实现效果有个最明显的现象,低优先级的用户列表部分只有在高优先级的输入框停止更新后才会最终更新,中间的几次后台渲染都被中断了。
用下面这张图可以解释当输入框快速输入hello
时,value
和deferredValue
的变化情况:
![](/assets/series/enjoy-react-hooks/08/render-interrupt.png)