不确定这是否是 React 问题。我有一个处于状态的对象数组,并将每个对象映射到一个组件。在这个组件内部,我有一个 useEffect 设置超时,当超时完成时,对象将从状态中删除,导致组件被卸载。我遇到的问题是,setTimeout 回调不会运行,直到所有当前的 setTimeouts 都已过期,然后它们同时运行。我通过向 setTimeout 回调添加 console.log 来确认这不是状态批处理,甚至在最后一个 setTimeout 完成之前也不会运行。
这是我映射组件的地方。 removeToast 是调用的函数,用于从状态数组中删除对象。
<div className="toastsContainer">
{toasts.map((toast, i) => (
<Toast key={i} toast={toast} removeToast={removeToast} />
))}
</div>
删除Toast功能:
const removeToast = (toast: IToast) => {
setToasts((prev) => prev.filter(({ id }) => id !== toast.id));
};
这是带有 setTimeout 的 Toast 组件
import { useEffect } from "react";
import IToast from "../interfaces/toast";
import "../styles/toast.css";
import { useSpring, animated } from "react-spring";
const TOAST_DURATION = 3000;
interface ToastProps {
toast: IToast;
removeToast: (toast: IToast) => void;
}
export function Toast({ toast, removeToast }: ToastProps) {
const { width } = useSpring({
from: { width: "100%" },
to: { width: "0%" },
config: { duration: TOAST_DURATION },
});
useEffect(() => {
const timerId = setTimeout(() => {
removeToast(toast);
}, TOAST_DURATION);
return () => clearTimeout(timerId);
}, [toast, removeToast]);
return (
<div className="toast">
<div className="message"> {toast.message}</div>
<animated.div
className="duration"
style={{
width,
}}
/>
</div>
);
}
尝试在父组件中使用 useEffect 并映射对象并在那里设置 setTimeout 。这只会导致每次添加对象时都会将多个 setTimeouts 应用于每个对象。
还尝试直接比较对象,而不是使用 ID,认为这可能是我的过滤功能。这并没有改变任何行为。
“设置状态函数”是异步的。当您调用该函数时,它所做的是将调用放入“异步队列”中,以便“在将来的某个时刻”执行。当该函数运行时,它将对拥有状态变量的组件进行排队以重新渲染。
React 在批处理一堆“设置状态函数”调用并在实际重新渲染拥有它们的组件之前运行大部分/全部调用方面相当不错。
由于看起来所有组件基本上都在同一超时(+/-几毫秒)下批量运行,因此您可能看到的是,直到所有子组件都渲染完毕后,父组件才会呈现。组件对
removeToast()
的调用(每个组件都调用 setToasts()
)完成其运行。
您可以尝试的一个实验:将索引值从
.map()
(即您的变量 i
)发送给每个孩子,并让他们将 Timeout 设置为 TOAST_DURATION * i
。然后看看应用程序的行为如何。