useCallback内的昂贵函数在数据无变更时仍被调用问题排查
这种情况完全可能发生,核心原因有以下几点:
- 你混淆了
useCallback的作用:useCallback仅用于缓存函数的引用,避免不必要的子组件重渲染,完全不会阻止函数本身被调用。只要onSuccess触发,不管useCallback返回的函数引用有没有变化,内部的昂贵函数都会执行。 - React Query的
onSuccess回调触发规则:无论数据是从新请求返回还是从本地缓存命中,只要查询成功拿到数据,onSuccess就会执行。你每次访问页面相当于组件重新挂载,useQuery会触发查询流程,哪怕返回的数据和之前完全相同,onSuccess也会被调用,自然会执行昂贵函数。 - 代码存在变量声明顺序错误:你先在
useDataQuery的参数中引用了callbackMemo,之后才用const声明callbackMemo,const存在暂时性死区,首次渲染时直接访问未声明的变量会抛出引用错误,即使运行时没有报错,首次传入的回调也是无效值。 useDataQuery的queryKey配置错误:当前的queryKey没有包含参数id,如果不同的id调用该Hook,会命中错误的缓存,也可能导致非预期的回调触发。
修复方案
你完全不需要把回调传给onSuccess,直接通过useEffect监听useQuery返回的data即可,React会对依赖项做引用对比,只有data的引用发生变化时才会执行副作用逻辑:
const [currentData, currentDataSet] = useState({}) // 无需传递回调 const dataQuery = useDataQuery(id) // 仅当data引用变化时执行 useEffect(() => { if (!dataQuery.data) return const updatedData = expensiveFunction(dataQuery.data) currentDataSet(updatedData) }, [dataQuery.data]) const expensiveFunction = (data) => { console.log("Called expensive function") let updatedData; // 数据处理逻辑 return updatedData; }
同时修改useDataQuery的queryKey,把id加入缓存键,避免不同id命中错误缓存:
export const useDataQuery = ( id: string, onSuccess?: Function ) => { return useQuery({ // 把id加入queryKey,不同id对应不同缓存 queryKey: [queryKeys.app.data, id], queryFn: async () => await getData( queryKeys.app.data, id ), onSuccess: (data) => { onSuccess && onSuccess(data); }, }); };
如果你的后端返回的数据内容相同但引用每次都变,可以在useEffect中加入深对比逻辑,确认内容确实变化后再执行昂贵函数。
内容的提问来源于stack exchange,提问作者monkeyWithAMachinegun
相关产品推荐
相关产品推荐

