javascript 具有同一组件的多个副本时setTimeout的问题

06odsfpq  于 2023-01-04  发布在  Java
关注(0)|答案(1)|浏览(132)

我已经试着调试这个问题几个小时了,但是我似乎一辈子都无法修复它。我正在尝试创建一个toast组件,每当创建一个toast时,它就会在列表中显示自己。它工作得很好,直到我尝试实现一个系统,在3秒后从列表中删除toast。

const Toast = (props) => {
    const [active, setActive] = useState(false)
    const { removeToast } = useToast()

    const timerRef = useRef(null)
    const timer2Ref = useRef(null)

    useEffect(() => {
        if (active) return; // attempt to prevent multiple timers from being created

        setActive(true)

        timerRef.current = setTimeout(() => {
            setActive(false)

            timer2Ref.current = setTimeout(() => { // Second timeout to remove the toast from the DOM after transition has finished
                removeToast(props.id)
            }, 300)

        }, 3000)

        return () => {
            // If clear, it prevents the last toast in the list from being removed?
            // If not cleared, strictmode causes issues (performs as expected with strictmode off)
            clearTimeout(timer2Ref.current)
            clearTimeout(timerRef.current)
        }
    }, [])

    return (
        <div className={`p-4  h-14 rounded-lg w-80 transition-all duration-300 flex-auto flex-0 ${props.className}`}
        /*${active ? 'translate-y-0' : 'translate-y-96'} Removed to recognise issue more clearly */ 
        >
            {props.message}
        </div>
    )
}

export default Toast

不管我做什么,它似乎都不会像预期的那样工作。如果我清除了超时,它会像预期的那样工作,但列表中的最后一个吐司永远不会被删除。
如果我不清除超时(我相信你应该这样做,以防止内存泄漏),所有的toast被删除似乎在同一时间,我认为这是因为StrictMode,因为与StrictMode禁用它的所有功能,如预期的。但我宁愿不要删除StrictMode只是为了这个。
这里还有列表组件(我不认为这是问题所在)

const ToastList = () => {
    const toastList = useSelector(state => state.toast.value)

    useEffect(() => {
        console.log(toastList)
    }, [toastList])

    if (toastList.length > 0) {
        return (
            <div className="flex flex-col-reverse pointer-events-none w-full z-50 fixed bottom-0 ">
                {toastList.map((toast, index) => (
                    <Toast message={toast.message } icon={toast.icon} key={index} id={toast.id} className={`mx-auto my-3 ${toast.color}`} />
                ))}
            </div>
        )
    }
}

export default ToastList
  • 不确定是否相关,但我通过Redux将所有toast存储在一个数组中。

--
我在这里四处查看了一下,发现另一个post看起来像是OP有类似的问题。所以我尝试通过一个ref使用removeToast函数,就像在帖子中看到的那样,但这并没有解决问题。
我也尝试过使用一个自定义的useTimeout钩子,但也没有解决它。
我自己也没有主意,所以任何帮助都将不胜感激:)此外,我没有太多的React经验,所以我道歉,如果这里观察到的问题是由于我缺乏知识哈哈,如果你可以看到任何不良做法,这里只是让我知道,所以我不会在未来犯这些错误

xtfmy6hx

xtfmy6hx1#

我在您的代码中看到的唯一明显的问题是使用toastList数组索引作为React键。使用数组索引作为键通常被认为是React中的反模式。可接受的例外是数组从未发生变化。这显然是代码中的一个问题,因为吐司消息可以在任何时候添加到toastList或从中删除。Map的toast.id是React键的最佳选项,因为它是唯一标识吐司消息的内在属性。
ToastList.jsx

import { useSelector } from "react-redux";
import Toast from "./Toast";

const ToastList = () => {
  const toastList = useSelector((state) => state.toast.value);

  return (
    <div className="flex flex-col-reverse pointer-events-none w-full z-50 fixed bottom-0 ">
      {toastList.map((toast) => (
        <Toast
          key={toast.id} // <-- use id as key
          className={`mx-auto my-3 ${toast.color}`}
          {...toast}
        />
      ))}
    </div>
  );
};

Toast组件***应***在超时到期之前提前卸载该组件的情况下清除任何运行超时。将超时入队逻辑拆分到一个useEffect挂接中,该挂接具有适当的依赖关系和清除函数,以便在组件卸载时清除超时。

import { useRef } from "react";
import { useEffect, useState } from "react";
import useToast from "../hooks/useToast";

const Toast = (props) => {
  const [active, setActive] = useState(false);
  const { removeToast } = useToast();

  const timerRef = useRef(null);
  const timer2Ref = useRef(null);

  useEffect(() => {
    // Only instantiate timers when toast inactive
    if (!active) {
      setActive(true);

      timerRef.current = setTimeout(() => {
        timer2Ref.current = setTimeout(() => {
          setActive(false);
          // Second timeout to remove it from the DOM after transition has finished
          removeToast(props.id);
        }, 300);
      }, 3000);
    }
  }, [active, props, removeToast]);

  // Mounting useEffect hook to return unmounting cleanup function.
  useEffect(
    () => () => {
      clearTimeout(timer2Ref.current);
      clearTimeout(timerRef.current);
    },
    []
  );

  return (
    <div
      className={`p-4 h-14 rounded-lg transition-all duration-300 flex flex-row text-slate-100 w-80 ${props.className}`}
      /*${active ? 'translate-y-0' : 'translate-y-96'} Removed to recognise issue more clearly */
    >
      {props.message}
    </div>
  );
};

export default Toast;

演示

相关问题