taoliujun / blog

https://taoliujun.github.io/blog/
https://taoliujun.github.io/blog/
0 stars 0 forks source link

React useDeferredValue在性能优化中的使用 #20

Open taoliujun opened 1 year ago

taoliujun commented 1 year ago

一个卡顿场景

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

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

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

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

//...

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

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

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

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

taoliujun commented 1 year ago

欢迎大家讨论