React Hooks 节流(throttle) / 防抖(debounce) 注意事项

812 阅读1分钟

前言

React 函数式组件使用 throttle / debounce 有诸多注意事项。

否则会有回调函数只是延迟执行,执行次数并没有减少的情况发生。

正文

useCallback

throttle / debounce 的回调函数如果不用 useCallback 包裹的话,在每一次状态更新的时候,回调函数都会是一个新的对象。

这就会导致 throttle / debounce 只会推迟回调函数的执行,并不会减少回调函数的调用次数。

如果用 useCallback 包裹回调函数,就会保证回调函数都是同一个。

dependencies

注意回调函数,如果内部有嵌套调用的话,每一层都需要走查,确保 useCallback 的 dependencies 中的任意一项,在这些回调函数中,每一项都没有被重新赋值。

踩坑实例

如下述的代码,ParentComponent 中,在 debounce 回调函数中被调用的 onChange 函数的依赖添加了不应该添加的 count,而 count 在 ChildComponent 中调用 debounceOnChange 前都会被赋予新值,这就导致 debounce 的回调函数每一次返回的也是新的。

就会出现虽然 debounce 了,但是回调函数只是延迟执行了,调用次数并没有减少的情况。

解决方法是去除 ParentComponent 的 onChange 函数中多余的依赖 count

const ChildComponent = ({ updateCount, onChange }) => {
    const [ownCount, setOwnCount] = useState(0);
    
    const debounceOnchange = useCallback(() => {
      debounce((otherDatas) => {
        onChange?.(otherDatas)
      }, 1000),
      [onChange]
    }, []);

    useEffect(() => {
        updateCount(ownCount);
        
        const otherDatas = someAsyncCalFunctions(ownCount);
        
        debounceOnchange(otherDatas);
    }, [ownCount]);

    return <div>
        xxxx
        <Button onClick={() => setOwnCount(v => v + 1)}>Click</Button>
    </div>
}
const ParentComponent = () => {
    const [count, setCount] = useState(0);
    
    const updateCount = useCallback((val) => {
        setCount(val);
    }, []);
    
    const onChange = useCallback((data) => {
            // do something using `data` & `otherDeps` 
            // but without `count`
        },
        [otherDeps, count]
    );
    
    return <div>
        xxx
        <ChildComponent 
            updateCount={updateCount}
            onChange={onChange}
        />
    </div>
}