React useDeferredValue在性能优化中的使用

发布 : 2023-07-05 分类 : React

原文链接:https://github.com/taoliujun/blog/issues/20

一个卡顿场景

已知浏览器在一帧时间里(默认16.6毫秒)要完成好多工作,其中最耗时的是js脚本执行和页面渲染。如果js脚本耗时太长,那要引起页面渲染掉帧,在用户的体验上就是卡顿。

这里有一个处理用户输入的搜索词语,将结果渲染到一个dom列表上的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { FC, useMemo, useState } from 'react';

const SearchResults: FC<{ query: string }> = ({ query }) => {
const datas = useMemo(() => {
return new Array(10000).fill(null).map(() => {
return `${query} ${Math.random()}`;
});
}, [query]);

if (!query) {
return null;
}

return (
<div>
<h2>search "{query}" list:</h2>
{datas.map((v, k) => {
return <p key={k}>{v}</p>;
})}
</div>
);
};

export const Main: FC = () => {
const [query, setQuery] = useState('');

return (
<>
Search:
<input
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
<SearchResults query={query} />
</>
);
};

当用户每次输入一个字符,就会触发SearchResults组件的重新渲染,这个渲染包括datas的重新计算,和dom结构的重新渲染,这个时间远远超过16毫秒,会导致下一个输入值的处理任务一直在等待中,造成卡顿。

useDeferredValue

React提供了时间切片的模式,这里不详细展开了,允许你在调度任务的过程中安排高优先级的任务,而useDeferredValue就是这个模式的一个hook,它可以延迟更新部分UI

在之前的代码中,我们稍作修改:

1
2
3
4
5
6
7
//...

const [query, setQuery] = useState('');
const defreredQuery = useDeferredValue(query);

//...
<SearchResults query={defreredQuery} />

当用户快速输入一个字符时,SearchResults组件的渲染就会被延迟,这样就尽量减少卡顿了。

useDeferredValue通过延迟状态的更新来实现这个目的,它不同于节流或防抖的固定时间控制,而是根据一系列复杂调度算法来决定延迟的时间,这样可以尽量减少卡顿的发生。