为何Lodash debounce搭配Apollo Client useLazyQuery仅首次防抖生效
问题原因
- 你通过
useCallback创建防抖实例时依赖项设为空数组,导致防抖函数从组件首次渲染后就固定,永远包裹的是第一次渲染时useLazyQuery返回的search函数实例。 useLazyQuery返回的search函数每轮渲染都会生成新的引用,且你在初始化useLazyQuery时直接将variables绑定了当前state的value,旧的search函数实例执行时的上下文和参数不符合当前渲染周期的预期,同时useLazyQuery在绑定的variables发生变化时如果配置了自动重取逻辑,也会直接触发查询,跳过防抖。- 包裹
console.log能正常生效的原因是该函数不依赖React的渲染上下文,也不会在每轮渲染生成新的引用,防抖逻辑不会被渲染周期干扰。
解决方法
方案1:调整useLazyQuery参数传递方式,调用时传入变量
不要在初始化useLazyQuery时绑定variables,调用search时手动传入最新的参数,避免变量更新触发自动查询:
const [value, setValue] = useState(''); // 移除初始化的variables配置 const [search, { loading, data }] = useLazyQuery(SEARCH_QUERY); // 防抖函数直接接收查询参数,每次调用取最新值 const debouncedSearch = React.useMemo(() => _.debounce((searchVal) => search({ variables: { searchString: searchVal } }), 1500) , [search]); // onChange事件中更新状态同时触发防抖查询 onChange={(e) => { const newVal = e.target.value; setValue(newVal); debouncedSearch(newVal); }}
方案2:用useRef同步最新的search函数实例
如果需要保留初始化variables的写法,可以通过ref同步每轮渲染生成的最新search函数,保证防抖逻辑执行的永远是当前有效实例:
const [value, setValue] = useState(''); const [search, { loading, data }] = useLazyQuery(SEARCH_QUERY, { variables: { searchString: value } }); const searchRef = useRef(search); // 每轮渲染更新ref存储的search引用 useEffect(() => { searchRef.current = search; }, [search]); const debouncedSearch = useMemo(() => _.debounce(() => searchRef.current(), 1500) , []); // onChange直接调用防抖函数即可 onChange={() => debouncedSearch()}
方案3:通过useEffect原生实现防抖逻辑
完全贴合React hooks的生命周期,避免第三方防抖函数和渲染周期冲突:
const [value, setValue] = useState(''); const [search, { loading, data }] = useLazyQuery(SEARCH_QUERY, { variables: { searchString: value } }); useEffect(() => { // 可根据需求调整空值判断逻辑 if (!value) return; const timer = setTimeout(() => { search(); }, 1500); // 依赖变化时自动清除定时器,天然实现防抖效果 return () => clearTimeout(timer); }, [value, search]); // onChange只需要更新状态即可 onChange={(e) => setValue(e.target.value)}
内容的提问来源于stack exchange,提问作者Erdna
相关产品推荐
相关产品推荐

