You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 23:27:29