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

React自定义useHook定时器未清理:弹窗快速开关后轮询仍运行

问题解决:组件卸载后轮询仍持续的修复方案

你的问题核心是组件卸载后,setInterval创建的轮询任务未被正确终止,导致API请求仍在持续触发。结合你的代码,以下是针对性的修复方案:

问题根源

  1. 当组件快速卸载/重新挂载时,旧的interval可能未被及时清除,新的interval又被创建,导致多个轮询任务同时运行;
  2. API请求完成后未检查组件挂载状态,即使组件已卸载,setClassDetailsData仍会执行,不仅引发React警告,还会让你误以为轮询未停止;
  3. 虽然依赖变化时React会执行上一次effect的清理函数,但手动在effect开头清除旧interval能避免极端场景下的遗漏。

修复后的代码

import { useEffect, useRef, useState } from 'react';
import { useFlags } from 'launchdarkly-react-client-sdk';
import {
  IPlannedEvent,
  IWaitlistEntry,
  ReservationItineraryDto,
} from 'typescript-scheduling-types';

import { getClassDetails } from '../actions';
import { logToNewRelic } from '../utils/logger';

export const usePolling = (classId: string): TUsePollingResponse => {
  const flags = useFlags();
  const [classDetailsData, setClassDetailsData] = useState<IPlannedEvent>();
  const [registrantData, setRegistrantData] = useState<ReservationItineraryDto[]>([]);
  const [waitlistData, setWaitlistData] = useState<IWaitlistEntry[]>([]);
  const pollingInterval = useRef<NodeJS.Timeout>();
  const isMounted = useRef(true); // 跟踪组件挂载状态

  useEffect(() => {
    // 每次effect执行前先清除旧的interval
    if (pollingInterval.current) {
      clearInterval(pollingInterval.current);
    }

    pollingInterval.current = setInterval(() => {
      const classSignInInteraction = window.newrelic ? window.newrelic.interaction() : null;

      if (classSignInInteraction) {
        classSignInInteraction.setName('Sign In Polling');
      }

      getClassDetails(classId, ['WaitlistEntries'])
        .then((data) => {
          // 仅在组件挂载时更新状态
          if (isMounted.current && data.id) {
            setClassDetailsData(data);
          }
        })
        .catch((error: any) => {
          logToNewRelic(error);
        })
        .finally(() => {
          if (classSignInInteraction) {
            classSignInInteraction.end();
          }
        });
    }, flags.classSignInPollingInterval || 60000);

    return () => {
      // 组件卸载时标记为未挂载,并清除interval
      isMounted.current = false;
      clearInterval(pollingInterval.current as NodeJS.Timeout);
    };
  }, [classId, flags.classSignInPollingInterval]);

  useEffect(() => {
    setRegistrantData(classDetailsData?.reservationItineraryItems || []);
    setWaitlistData(classDetailsData?.waitlistEntries || []);
  }, [classDetailsData]);

  return {
    newClassDetailsData: classDetailsData,
    newRegistrantData: registrantData,
    newWaitlistData: waitlistData,
    pollingInterval: pollingInterval.current,
  };
};

export type TUsePollingResponse = {
  newClassDetailsData: IPlannedEvent | undefined;
  newRegistrantData: ReservationItineraryDto[];
  newWaitlistData: IWaitlistEntry[];
  pollingInterval: NodeJS.Timeout | undefined;
};

关键修改点

  • 添加isMounted ref:在API请求成功后,仅当组件仍挂载时才更新状态,避免不必要的状态更新和React警告;
  • 每次effect执行前清除旧interval:确保不会同时存在多个轮询任务;
  • 清理函数中标记组件为未挂载:彻底阻断后续的状态更新逻辑。

内容的提问来源于stack exchange,提问作者jrock2004

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:57:38