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

如何解决React+NextJS中带超时配置的Toast随机删除问题

React Context 实现Toast随机异常删除问题修复

问题场景

项目技术栈为 React + NextJS + Tailwind CSS,基于 React Context 封装Toast组件,核心逻辑为:

  • 每个Toast支持timeout配置,值为0时需用户手动关闭,否则到达配置时长(默认3000ms)后自动关闭
  • 同时触发自动关闭、手动关闭类型的Toast时,会出现Toast无规律随机被删除的异常

问题相关核心代码如下:

export const ToastContextProvider:FC<ToastProps> = ({ children }) => {
  const [toasts, setToasts] = useState<ToastComponentProps[]>([]);

  const generateId = () => Math.random().toString(36).substring(2, 9);

  const removeToast = (_id: string): void => {
    setToasts(toasts.filter(({ id }) => id !== _id));
  };

  const showToast = (toast: ToastComponentProps) => {
    const toastWithId = { ...toast, id: generateId() };
    setToasts((oldToasts) => [...oldToasts, toastWithId]);
    if (toast.timeout !== 0) {
      setTimeout(() => removeToast(toastWithId.id), toast.timeout ?? 3000);
    }
  };
  // 其余上下文逻辑省略
}

问题根因

异常来自React闭包陷阱:

  • removeToast方法中更新状态时,直接使用了当前渲染周期闭包捕获的toasts变量,没有采用函数式更新获取最新状态
  • 连续触发多个Toast时,不同自动关闭定时器触发时,调用的removeToast持有的是对应渲染节点的旧toasts快照,执行filter操作时会丢失快照生成后新增的Toast,相当于用旧的状态覆盖了最新的列表,最终表现为Toast随机被错删、漏删。

修复代码

核心修复点是将removeToast中的状态更新改为函数式写法,保证每次执行删除时都能拿到最新的Toast列表,可选优化是存储定时器ID、用useCallback包装方法减少不必要的上下文重渲染:

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

export const ToastContextProvider:FC<ToastProps> = ({ children }) => {
  const [toasts, setToasts] = useState<ToastComponentProps[]>([]);
  // 存储定时器ID,组件卸载时清理避免内存泄漏
  const timerRef = useRef<Record<string, NodeJS.Timeout>>({});

  const generateId = () => Math.random().toString(36).substring(2, 9);

  // 函数式更新获取最新状态,不依赖闭包捕获的toasts值
  const removeToast = useCallback((_id: string): void => {
    setToasts((latestToasts) => latestToasts.filter(({ id }) => id !== _id));
    // 清理对应定时器
    if (timerRef.current[_id]) {
      clearTimeout(timerRef.current[_id]);
      delete timerRef.current[_id];
    }
  }, []);

  const showToast = useCallback((toast: ToastComponentProps) => {
    const toastWithId = { ...toast, id: generateId() };
    setToasts((oldToasts) => [...oldToasts, toastWithId]);
    if (toast.timeout !== 0) {
      const timer = setTimeout(() => removeToast(toastWithId.id), toast.timeout ?? 3000);
      timerRef.current[toastWithId.id] = timer;
    }
  }, [removeToast]);

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

  // 其余上下文传递逻辑省略
}

修复后不管同时触发多少个不同配置的Toast,删除逻辑都会基于最新的列表执行,不会再出现随机错删的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 12:18:17