React自定义Toast中setState配合setTimeout导致已删元素复现问题
问题根因
代码触发了React状态操作的典型闭包陷阱:
removeToast方法中直接引用了当前渲染上下文的toasts状态,而createToast中注册的setTimeout回调会保留创建回调时的上下文快照,5秒后执行时拿到的toasts不是最新的状态值,而是创建定时器那一刻的旧状态。- 单次创建toast时旧状态和最新状态一致,所以运行正常;多次并发创建toast时,每个定时器回调拿到的都是创建对应toast时的旧状态,删除操作就会出现状态覆盖、误删其他toast的异常。
- 手动删除功能正常是因为手动触发删除时,调用的是最新渲染上下文的
removeToast,拿到的toasts是最新状态,不会出现闭包问题。
修复方案
只需要把removeToast中的状态更新改为函数式更新即可,函数式更新的入参永远是当前最新的状态值,不受闭包影响:
const useToastsContainer = createContainer(() => { const [toasts, setToasts] = useState<ToastItem[]>([]); useEffect(() => { console.log(toasts); }, [toasts]); const removeToast = (id: string) => { // 改为函数式更新,用prevToasts获取最新状态 setToasts(prevToasts => prevToasts.filter((item) => item.id !== id)); }; const createToast = (type: ToastType, content: string) => { const id = v4(); const toast = { id, type, content, }; // 可选优化:这里也改为函数式更新,避免极端并发场景的状态覆盖问题 setToasts(prevToasts => [...prevToasts, toast]); setTimeout(() => { removeToast(id); }, 5000); }; return { toasts, removeToast, createToast, }; });
内容的提问来源于stack exchange,提问作者Dawid Szemborowski
相关产品推荐
相关产品推荐

