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

如何清理React Provider中的多个Timeout,避免定时器残留?

问题描述

我有如下React Alert Provider代码:

import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';

export const AlertContext = createContext({
  addAlert: () => {},
  alerts: [],
  clearAlert: () => {},
  setAlerts: () => {},
});

export function useAlert() {
  return useContext(AlertContext);
}

export function AlertProvider(props: { children: React.ReactNode }) {
  const [alerts, setAlerts] = useState([]);

  const clearAlert = useCallback(
    (idToRemove: string | null) => {
      // Clear a specific alert by ID.
      if (idToRemove) {
        setAlerts(alerts => alerts.filter(a => a.id !== idToRemove));
        return;
      }
    },
    [],
  );

  const addAlert = useCallback(
    (alert: any, options?: { autoDismissTime?: number }) => {
      setAlerts(alerts => {
  
        if (options?.autoDismissTime) {
          setTimeout(() => {
            clearAlert(alert.id);
          }, options.autoDismissTime)
        }

        return [...alerts, alert];
      });
    },
    [clearAlert],
  );

  return (
    <AlertContext.Provider value={{ alerts, setAlerts, addAlert, clearAlert }}>
      {props.children}
      <ClientOnly>
        {() => <AlertContainer alerts={alerts} clearAlert={clearAlert} />}
      </ClientOnly>
    </AlertContext.Provider>
  );
}

在组件中调用方式如下:

const { addAlert } = useAlert();
// 添加提示框
addAlert(...)

我需要确保Provider中不会残留Timeout,要正确管理多个定时器(每个设置了autoDismissTime的Alert对应一个定时器)。


解决方案

通过useRef存储定时器ID与Alert ID的映射,在Alert被清除或Provider卸载时同步清理定时器,具体实现如下:

1. 新增定时器映射的Ref

在AlertProvider中添加ref,用于追踪每个Alert对应的定时器ID:

const timerRef = useRef<Record<string, NodeJS.Timeout>>({});

2. 修改addAlert方法

创建定时器后将ID存入ref,同时处理重复ID的Alert(清理旧定时器):

const addAlert = useCallback(
  (alert: any, options?: { autoDismissTime?: number }) => {
    setAlerts(prevAlerts => {
      // 若存在相同ID的Alert,先清理旧定时器
      if (timerRef.current[alert.id]) {
        clearTimeout(timerRef.current[alert.id]);
        delete timerRef.current[alert.id];
      }

      if (options?.autoDismissTime) {
        const timerId = setTimeout(() => {
          clearAlert(alert.id);
        }, options.autoDismissTime);
        // 存储当前Alert的定时器ID
        timerRef.current[alert.id] = timerId;
      }

      return [...prevAlerts, alert];
    });
  },
  [clearAlert],
);

3. 修改clearAlert方法

清除Alert的同时,同步清理对应的定时器并从ref中移除:

const clearAlert = useCallback(
  (idToRemove: string | null) => {
    if (idToRemove) {
      // 清除对应定时器
      if (timerRef.current[idToRemove]) {
        clearTimeout(timerRef.current[idToRemove]);
        delete timerRef.current[idToRemove];
      }
      setAlerts(prevAlerts => prevAlerts.filter(a => a.id !== idToRemove));
    }
  },
  [],
);

4. Provider卸载时清除所有剩余定时器

添加useEffect,在组件卸载时遍历ref清除所有未触发的定时器:

useEffect(() => {
  return () => {
    // 清理所有残留定时器
    Object.values(timerRef.current).forEach(timerId => clearTimeout(timerId));
    timerRef.current = {};
  };
}, []);

完整修改后的代码

import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';

export const AlertContext = createContext({
  addAlert: () => {},
  alerts: [],
  clearAlert: () => {},
  setAlerts: () => {},
});

export function useAlert() {
  return useContext(AlertContext);
}

export function AlertProvider(props: { children: React.ReactNode }) {
  const [alerts, setAlerts] = useState([]);
  // 存储Alert ID到定时器ID的映射
  const timerRef = useRef<Record<string, NodeJS.Timeout>>({});

  const clearAlert = useCallback(
    (idToRemove: string | null) => {
      if (idToRemove) {
        // 清除对应定时器
        if (timerRef.current[idToRemove]) {
          clearTimeout(timerRef.current[idToRemove]);
          delete timerRef.current[idToRemove];
        }
        setAlerts(prevAlerts => prevAlerts.filter(a => a.id !== idToRemove));
      }
    },
    [],
  );

  const addAlert = useCallback(
    (alert: any, options?: { autoDismissTime?: number }) => {
      setAlerts(prevAlerts => {
        // 若存在相同ID的Alert,先清理旧定时器
        if (timerRef.current[alert.id]) {
          clearTimeout(timerRef.current[alert.id]);
          delete timerRef.current[alert.id];
        }

        if (options?.autoDismissTime) {
          const timerId = setTimeout(() => {
            clearAlert(alert.id);
          }, options.autoDismissTime);
          timerRef.current[alert.id] = timerId;
        }

        return [...prevAlerts, alert];
      });
    },
    [clearAlert],
  );

  // 组件卸载时清除所有定时器
  useEffect(() => {
    return () => {
      Object.values(timerRef.current).forEach(timerId => clearTimeout(timerId));
      timerRef.current = {};
    };
  }, []);

  return (
    <AlertContext.Provider value={{ alerts, setAlerts, addAlert, clearAlert }}>
      {props.children}
      <ClientOnly>
        {() => <AlertContainer alerts={alerts} clearAlert={clearAlert} />}
      </ClientOnly>
    </AlertContext.Provider>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 06:03:20