React 18自定义Hook无限重渲染触发无限请求如何解决
无需要求调用方包裹useMemo/useCallback的解决方案
核心思路是把依赖稳定性的处理逻辑完全收敛到Hook内部,不把稳定性成本转嫁给调用方,具体实现如下:
实现逻辑
- 利用ref存储Hook入参的最新值,从根源避免闭包陈旧值问题
- 对传入的
variables、mapOptions做深比较,仅当实际内容/逻辑变化时才更新缓存值 loadOptions函数本身用空依赖的useCallback包裹,保证返回的函数引用永久稳定,不会随父组件重渲染变化
改造后的Hook代码
import { useCallback, useRef, useEffect } from 'react'; // 若项目未引入lodash,可自行实现简易深比较逻辑,适配普通对象、数组场景即可 import isEqual from 'lodash/isEqual'; export const useOptionsLoader = <Q extends RequiredResponse, V extends RequiredVariables>({ lazyQuery, variables, mapOptions, }: LoadSelectOptionsProps<Q, V>) => { // 初始化ref存储入参 const latestConfigRef = useRef({ lazyQuery, variables, mapOptions }); // 仅当入参实际内容变化时,才更新ref存储的值 useEffect(() => { const prevConfig = latestConfigRef.current; // Apollo返回的lazyQuery本身引用稳定,不需要深比较 const isLazyQuerySame = prevConfig.lazyQuery === lazyQuery; const isVariablesSame = isEqual(prevConfig.variables, variables); // 函数可通过toString对比实现逻辑是否变化,若需要更严谨判断可结合useEvent相关实现 const isMapSame = prevConfig.mapOptions.toString() === mapOptions.toString(); if (!isLazyQuerySame || !isVariablesSame || !isMapSame) { latestConfigRef.current = { lazyQuery, variables, mapOptions }; } }, [lazyQuery, variables, mapOptions]); const loadOptions = useCallback( async (keyword: string, extra = { page: 0 }) => { // 执行时直接从ref取最新配置,不存在闭包陈旧问题 const { lazyQuery: currentLazyQuery, variables: currentVariables, mapOptions: currentMapOptions } = latestConfigRef.current; const { data } = await currentLazyQuery({ variables: { ...currentVariables, keyword, page: extra.page, // 原有请求参数逻辑保持不变 } }); return currentMapOptions(data) || []; }, [] // 空依赖,loadOptions引用永久稳定 ); return loadOptions; };
方案优势
- 调用方原有代码不需要任何改动,直接传内联对象、内联函数作为入参即可,不需要手动包裹
useMemo/useCallback - 返回的
loadOptions引用不会随父组件重渲染无意义变化,彻底解决下游组件因为props变化无限触发请求、重渲染崩溃的问题 - 不存在闭包拿到旧值的问题,每次执行loadOptions都会读取最新的查询参数、映射逻辑
注意事项
- 若不想引入lodash的isEqual,可自行实现适配业务场景的简易深比较,因为GraphQL查询的variables一般都是可序列化的普通JSON对象,对比性能开销极低
- 如果React版本升级到18以上支持
useEvent,可以直接用useEvent包裹loadOptions,进一步简化ref更新的逻辑,效果完全一致 - 不要用深比较版本的useCallback直接把三个入参作为依赖,那样每次渲染还是会执行深比较逻辑,空依赖+ref的方案性能最优
内容的提问来源于stack exchange,提问作者Crocsx
相关产品推荐
相关产品推荐

