如何清理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
相关产品推荐
相关产品推荐

