如何避免页面刷新重置值?实现猜歌应用每日换歌方案
实现每日固定更换歌曲的猜歌应用方案
一、Wordle的核心实现逻辑
Wordle靠**固定UTC时间窗口(每日0点切换)**来锁定当日谜题,核心逻辑是:
- 提前将所有谜题按顺序存入列表/数据库
- 计算当前日期与某个固定起始日的天数差,用这个差值作为索引从列表中取出当日谜题
- 前端用
localStorage记录用户当日的游戏状态,但谜题本身的选择完全基于日期计算,同一日期内所有用户看到的内容一致,刷新页面也不会改变
二、是否需要后端API支持?
分两种场景:
- 纯前端方案:如果你的歌曲列表固定、不需要动态更新,完全可以在前端实现,无需后端。
- 后端方案:如果需要动态维护歌曲库、或者想隐藏歌曲列表(防止用户提前查看),后端API是必要的——由后端根据日期返回当日歌曲,前端仅负责播放和交互。
结合你已经准备了Node/Express和MongoDB,推荐用后端方案,扩展性更强。
三、针对你的Remix项目的具体实现方案
方案1:纯前端快速落地
无需后端,基于日期计算固定的歌曲索引:
- 先写一个计算日期偏移量的工具函数:
// 计算当前UTC日期与自定义起始日的天数差 const getDayOffset = () => { const startDate = new Date('2024-01-01'); // 可自定义起始日期 const today = new Date(); today.setUTCHours(0, 0, 0, 0); startDate.setUTCHours(0, 0, 0, 0); const diffTime = today.getTime() - startDate.getTime(); return Math.floor(diffTime / (1000 * 60 * 60 * 24)); };
- 修改你的
useEffect逻辑,用日期偏移量替代随机数:
useEffect(() => { const dayOffset = getDayOffset(); // 取模确保索引在歌曲列表范围内 const songIndex = dayOffset % songList.length; randomSongIndex.current = songIndex; const targetSong = songList[songIndex]; const audio = new Audio(targetSong); audioRef.current = audio; audio.addEventListener("loadedmetadata", () => { // 基于日期生成固定的起始播放位置,确保同一日播放起点一致 const pseudoRandom = (seed) => { const x = Math.sin(seed) * 10000; return x - Math.floor(x); }; const startTime = Math.floor(pseudoRandom(dayOffset) * (audio.duration - 6)); startTimeRef.current = startTime; }); return () => { audio.removeEventListener("loadedmetadata", () => {}); }; }, []);
这样不管怎么刷新页面,同一UTC日期内都会加载同一首歌,播放起点也固定。
方案2:后端API实现(推荐)
利用你已有的Node/Express和MongoDB:
- 后端接口实现:
- 在MongoDB中存储歌曲文档,包含
url、title等字段 - 编写
GET /api/daily-song接口,根据日期返回当日歌曲:
- 在MongoDB中存储歌曲文档,包含
app.get('/api/daily-song', async (req, res) => { const startDate = new Date('2024-01-01'); const today = new Date(); today.setUTCHours(0, 0, 0, 0); const diffTime = today.getTime() - startDate.getTime(); const dayOffset = Math.floor(diffTime / (1000 * 60 * 60 * 24)); const songCount = await Song.countDocuments(); const targetIndex = dayOffset % songCount; const dailySong = await Song.findOne().skip(targetIndex); res.json(dailySong); });
- 前端调用接口:
- 在
InputSection组件中通过useEffect获取当日歌曲:
- 在
useEffect(() => { const fetchDailySong = async () => { const res = await fetch('/api/daily-song'); const song = await res.json(); const audio = new Audio(song.url); audioRef.current = audio; audio.addEventListener("loadedmetadata", () => { const dayOffset = getDayOffset(); const pseudoRandom = (seed) => { const x = Math.sin(seed) * 10000; return x - Math.floor(x); }; const startTime = Math.floor(pseudoRandom(dayOffset) * (audio.duration - 6)); startTimeRef.current = startTime; }); }; fetchDailySong(); }, []);
四、React钩子相关说明
没有专门的React内置钩子直接实现“每日固定内容”,核心思路是用日期作为唯一标识,结合useEffect在组件初始化时获取对应内容。另外可以用localStorage记录用户当日的猜题状态(比如是否已猜对),避免页面刷新后重复操作。
五、现有代码优化点
- 当前代码用
useRef存随机索引,组件初始化时的useEffect空依赖只会执行一次,但页面刷新会重新执行导致换歌,改成基于日期的索引就能解决。 - 播放时长的
setTimeout(1000)是固定1秒,建议改成基于需求的固定时长(比如6秒):setTimeout(() => { ... }, 6000),更符合猜歌场景。
内容的提问来源于stack exchange,提问作者Lemon
相关产品推荐
相关产品推荐

