React触发超时函数时使用旧状态数据的问题排查
React通知组件自动关闭时状态过期问题的解决办法
你开发的React通知提供者中,手动关闭通知功能正常,但自动超时关闭时,父组件的finished函数会用到旧的notifications状态,导致无法正确移除对应通知。这是React闭包特性导致的——超时函数捕获了组件初始渲染时的状态快照,而非最新状态。不需要重新设计功能,只需要调整状态更新方式和依赖项就能解决。
问题根源
- 父组件的
finished、info等方法直接依赖当前notifications状态,当超时触发这些方法时,闭包保留的是函数创建时的旧状态数组。 - 子组件
NotificationCard的useEffect依赖项为空数组,仅在组件挂载时创建超时,捕获的是初始的close函数和notification值,无法响应后续状态变化。
解决方案
1. 父组件改用函数式更新状态
将所有修改notifications的方法改为函数式更新,确保每次都基于最新状态计算新数组:
export const NotificationProvider = ({ children }: { children?: JSX.Element }) => { const [notifications, setNotifications] = useState<Notification[]>([]); // 省略其他不变代码... const info = (notification: Notification) => { setNotifications(prev => [...prev, createNotification({ ...notification, level: NotificationLevel.INFO })]); }; const success = (notification: Notification) => { setNotifications(prev => [...prev, createNotification({ ...notification, level: NotificationLevel.SUCCESS })]); }; const warning = (notification: Notification) => { setNotifications(prev => [...prev, createNotification({ ...notification, level: NotificationLevel.WARNING })]); }; const error = (notification: Notification) => { setNotifications(prev => [...prev, createNotification({ ...notification, level: NotificationLevel.ERROR })]); }; const finished = (notification: Notification) => { setNotifications(prev => prev.filter((n) => n.id !== notification.id)); }; // 省略return部分... };
2. 修复子组件的依赖项与函数缓存
调整子组件的useEffect依赖项,并用useCallback缓存close函数,避免不必要的重渲染和闭包问题:
import styles from "@styles/Notification.module.scss"; import { Notification } from "@utils/notificationProvider"; import { useEffect, useState, useCallback } from "react"; const NotificationCard = ({ notification, onFinished }: { notification: Notification; onFinished?: (notification: Notification) => void; }) => { const [isOpen, setIsOpen] = useState(true); const close = useCallback(() => { setIsOpen(false); setTimeout(() => { if (onFinished) { onFinished(notification); } }, 500); }, [notification, onFinished]); useEffect(() => { const timeout = setTimeout(() => { close(); }, notification.timeout); return () => { clearTimeout(timeout); }; }, [notification.timeout, close]); return ( <div onClick={() => close()} className={isOpen ? styles.notificationCard : styles.notificationCardClosed}> {notification.message} </div> ); }; export default NotificationCard;
说明
- 函数式更新:
setNotifications(prev => ...)让React始终基于最新的状态快照计算新数组,彻底规避闭包导致的旧状态问题。 useCallback:缓存close函数,仅当notification或onFinished变化时才重新创建,避免useEffect频繁触发。- 完善依赖项:确保
useEffect能响应notification.timeout的变化,同时保证超时逻辑使用最新的close函数。
内容的提问来源于stack exchange,提问作者Andreas Halvorsen Tollånes
相关产品推荐
相关产品推荐

