React日期对比抽奖页面问题:触发日期时重复渲染及函数多次调用
解决React抽奖网站日期匹配时重复渲染问题
问题出在这两个地方:
- 直接在组件渲染阶段执行日期判断和
setWinner,一旦时间匹配,每次渲染都会触发状态更新,导致React无限重复渲染,RandomNumber被反复调用。 - 用
toLocaleDateString/toLocaleTimeString做日期对比,容易受系统地区、格式差异影响;之前尝试的毫秒对比可能因为日期字符串解析错误(比如"12/12/2022"的解析结果受时区干扰),导致匹配逻辑被跳过。
下面是修复后的完整代码和关键改动说明:
修复后的Datetime组件
import { getRandomWinner } from "components/RandomNumber"; import { useEffect, useState, useRef } from "react"; import "./styles.css"; const Datetime = () => { const [winner, setWinner] = useState(""); const [dateState, setDateState] = useState(new Date()); const timerRef = useRef<NodeJS.Timeout | null>(null); // 用ISO格式定义目标日期,避免时区解析问题,转换成时间戳方便对比 const targetDate = new Date("2022-12-12T10:33:00"); const targetTimestamp = targetDate.getTime(); useEffect(() => { // 组件挂载时创建一次定时器,存到ref里方便后续清除 timerRef.current = setInterval(() => setDateState(new Date()), 1000); return () => { if (timerRef.current) clearInterval(timerRef.current); }; }, []); useEffect(() => { // 已经生成获奖者就不再执行逻辑 if (winner) return; const currentTimestamp = dateState.getTime(); // 允许±1秒误差,覆盖定时器精度和系统时间的微小偏差 if (Math.abs(currentTimestamp - targetTimestamp) <= 1000) { const newWinner = getRandomWinner(); setWinner(newWinner); // 生成获奖者后可以停止定时器,节省资源 if (timerRef.current) clearInterval(timerRef.current); } }, [dateState, winner, targetTimestamp]); const currentDate = dateState.toLocaleDateString("pt-BR", { day: "numeric", month: "long", year: "numeric", }); const currentTime = dateState.toLocaleString("pt-BR", { hour: "numeric", minute: "numeric", second: "numeric", hour12: true, }); return ( <> <p className="date-custom">{currentDate}</p> <p className="time-custom">{currentTime}</p> {winner && <div className="winner">获奖者:{winner}</div>} </> ); }; export default Datetime;
优化后的RandomNumber工具函数(改成纯函数,避免组件冗余)
export const getRandomWinner = () => { const items = [ 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 13, 14, 19, 20, 21, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 49, 50, 56, ]; const item = items[Math.floor(Math.random() * items.length)]; const numberToName = (item: number): string => { switch(item) { case 1: return `Philipe Jensen ${item}`; case 2: return `Jão ${item}`; case 3: return `Xepa ${item}`; case 5: case 7: case 10: case 11: case 15: case 19: case 26: case 29: case 30: case 50: return `Leleo ${item}`; case 8: return `Felipe ${item}`; case 9: case 21: return `Pimpão ${item}`; case 12: return `Monteiro ${item}`; case 13: return `May ${item}`; case 20: return `Bugs ${item}`; case 24: case 56: return `Vev ${item}`; case 27: return `Tic ${item}`; case 28: return `Helo Ams ${item}`; case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: return `Izumi ${item}`; case 49: return `Geobanna ${item}`; default: return ''; } }; return numberToName(item); };
关键改动说明
- 精准日期对比:用ISO格式创建目标日期,转换成时间戳后对比,还加入±1秒误差范围,解决时区解析问题和定时器精度不足导致的匹配遗漏。
- 避免无限渲染:把获奖者生成逻辑移到
useEffect中,依赖dateState和winner,只有时间变化且未生成获奖者时才执行,杜绝渲染阶段触发状态更新的循环。 - 优化定时器:用
useRef存储定时器ID,组件挂载时只创建一次,生成获奖者后可直接清除,节省资源。 - 简化抽奖逻辑:把
RandomNumber从React组件改成纯工具函数,避免组件渲染的额外开销,调用更直接。
内容的提问来源于stack exchange,提问作者Doug
相关产品推荐
相关产品推荐

