React自定义useHook定时器未清理:弹窗快速开关后轮询仍运行
问题解决:组件卸载后轮询仍持续的修复方案
你的问题核心是组件卸载后,setInterval创建的轮询任务未被正确终止,导致API请求仍在持续触发。结合你的代码,以下是针对性的修复方案:
问题根源
- 当组件快速卸载/重新挂载时,旧的
interval可能未被及时清除,新的interval又被创建,导致多个轮询任务同时运行; - API请求完成后未检查组件挂载状态,即使组件已卸载,
setClassDetailsData仍会执行,不仅引发React警告,还会让你误以为轮询未停止; - 虽然依赖变化时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; };
关键修改点
- 添加
isMountedref:在API请求成功后,仅当组件仍挂载时才更新状态,避免不必要的状态更新和React警告; - 每次effect执行前清除旧interval:确保不会同时存在多个轮询任务;
- 清理函数中标记组件为未挂载:彻底阻断后续的状态更新逻辑。
内容的提问来源于stack exchange,提问作者jrock2004
相关产品推荐
相关产品推荐

