在处理Web端事件时,我们经常需要对响应的Callback函数进行防抖(debounce)和节流(throttle)处理。然而在React 函数式组件中对回调函数进行防抖处理时,会遇到意想不到的结果:
import React, {useState} from "react";import { debounce } from "lodash";const sendQuery = (query) => console.log(`Querying for ${query}`);const Search = () => {const [userQuery, setUserQuery] = useState("");const delayedQuery = debounce(q => sendQuery(q), 500);const onChange = e => {setUserQuery(e.target.value);delayedQuery(e.target.value);};return (<div><label>Search:</label><input onChange={onChange} value={userQuery} /></div>);}
这里我们期望的结果是500ms内, 无论delayedQuery触发多少次,都只会执行最后一次。然而现实却是我们所有的输入都会在500ms后触发网络请求。
我们知道,函数组件每次重绘都会导致在函数内部定义的变量都会被重新初始化。这就意味着,每次我们调用debounce函数时都会重新注册一个setTimeout回调。
解决这个问题可以采用两种办法。
使用useRef来维持一个跨函数执行的回调函数引用:
const SearchFixed = () => {const [userQuery, setUserQuery] = useState("");const delayedQuery = useRef(debounce(q => sendQuery(q), 500)).current;const onChange = e => {setUserQuery(e.target.value);delayedQuery(e.target.value);};return (<div><label>Search Fixed:</label><input onChange={onChange} value={userQuery} /></div>);};
useRef所返回的值被缓存了起来,因此每次函数组件重绘不会导致debounce(…)的重复执行。
还有一种更方便的方法就是使用useCallback,声明只在组件初始化时创建debounce函数。
const delayedQuery = useCallback(debounce(q => sendQuery(q), 500), []);
