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