React多预订场景下useState计时器冲突问题的解决方法
解决方案:多预订独立计时器实现
核心思路
把单个状态改成按预订唯一ID存储对应倒计时的对象,同时用useEffect统一管理每个未支付预订的计时器,避免状态冲突和内存泄漏。
具体实现步骤
1. 修改状态存储结构
将单个timeOut状态改为存储多个预订倒计时的对象,key用MongoDB生成的预订唯一ID(如item._id):
const [countdowns, setCountdowns] = React.useState({});
2. 编写纯计算倒计时的工具函数
剥离状态更新逻辑,只做倒计时计算:
// 计算单个预订的剩余时间,返回格式化字符串或"已过期" const calculateCountdown = (createdAt) => { const expireTime = new Date(createdAt).getTime() + 1800000; // 30分钟后过期 const now = new Date().getTime(); const distance = expireTime - now; if (distance <= 0) { return "已过期"; } // 格式化分钟和秒为两位数字 const min = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)).toString().padStart(2, '0'); const sec = Math.floor((distance % (1000 * 60)) / 1000).toString().padStart(2, '0'); return `${min}:${sec}`; };
3. 用useEffect统一管理计时器
遍历未支付预订,为每个预订启动独立计时器,并在组件卸载/预订列表变化时清除:
// 假设你的预订列表数据存在bookings变量中 React.useEffect(() => { const timers = []; bookings.forEach(item => { if (item.status === 'unPaid') { // 初始化当前预订的倒计时 setCountdowns(prev => ({...prev, [item._id]: calculateCountdown(item.createdAt)})); // 启动每秒更新的计时器 const timer = setInterval(() => { const currentCountdown = calculateCountdown(item.createdAt); setCountdowns(prev => ({...prev, [item._id]: currentCountdown})); // 过期后清除计时器,可触发数据刷新同步MongoDB状态 if (currentCountdown === "已过期") { clearInterval(timer); // 这里可以调用刷新预订列表的API,比如fetchBookings() } }, 1000); timers.push(timer); } }); // 清理函数:组件卸载或预订列表变化时清除所有计时器 return () => timers.forEach(timer => clearInterval(timer)); }, [bookings]);
4. 渲染时获取对应预订的倒计时
直接通过预订ID从countdowns对象中取值:
<th>{item.status === 'unPaid' ? countdowns[item._id] : ''}</th>
关键注意事项
- 必须用预订的唯一ID作为状态对象的key,确保每个预订的倒计时独立存储。
- 用
useEffect的清理函数清除计时器,避免内存泄漏和无效的状态更新。 - 倒计时过期后建议触发数据刷新,让前端同步MongoDB中已删除的预订数据。
内容的提问来源于stack exchange,提问作者Peret
相关产品推荐
相关产品推荐

