React接收新通知时如何播放声音?有无更优于setInterval的数据获取方案?
实现方案
新通知自动播放提示音实现
核心逻辑是拉取新数据后和本地已有数据比对,仅当存在新增记录时触发提示音,同时适配浏览器自动播放限制,具体实现步骤如下:
- 提前将提示音文件放入项目静态资源目录,全局初始化一次音频实例,避免重复创建导致的播放异常
- 拉取接口数据后,通过记录ID判断是否存在新增条目,首次加载不触发提示音,仅更新本地记录标记
- 处理浏览器自动播放限制:浏览器默认禁止无用户交互的音频播放,需要在用户首次进入页面产生点击/触摸交互时,提前解锁音频播放权限
改造后的完整参考代码:
import { useEffect, useRef, useState } from 'react'; // 全局初始化提示音实例,仅创建一次 const notifyAudio = new Audio('/notification.mp3'); notifyAudio.volume = 0.5; // 可根据需求调整音量 export default function ComplainList() { const [data, setData] = useState([]); // 用ref存储最新一条记录的ID,规避闭包导致的取值异常 const lastNewestIdRef = useRef(null); // 标记音频是否已完成解锁 const audioUnlockedRef = useRef(false); // 监听用户首次交互,解锁音频播放权限 useEffect(() => { const unlockAudioHandler = () => { if (audioUnlockedRef.current) return; notifyAudio.play() .then(() => { notifyAudio.pause(); notifyAudio.currentTime = 0; audioUnlockedRef.current = true; }) .catch(() => {}); // 解锁完成后移除交互监听 document.removeEventListener('click', unlockAudioHandler); document.removeEventListener('touchstart', unlockAudioHandler); }; document.addEventListener('click', unlockAudioHandler); document.addEventListener('touchstart', unlockAudioHandler); return () => { document.removeEventListener('click', unlockAudioHandler); document.removeEventListener('touchstart', unlockAudioHandler); }; }, []); async function getData() { const res = await fetch(`${API_ENDPOINT}/api/listComplain`); const result = await res.json(); const newestItem = result[0]; // 接口默认按创建时间倒序时,第一条为最新数据 // 首次拉取仅存储最新ID,不触发提示 if (lastNewestIdRef.current === null) { lastNewestIdRef.current = newestItem?.id || 0; setData(result); return; } // 最新记录ID大于本地存储的ID,说明存在新通知 if (newestItem && newestItem.id > lastNewestIdRef.current) { notifyAudio.play().catch(err => console.log('提示音播放失败:', err)); lastNewestIdRef.current = newestItem.id; } setData(result); } // 原有轮询逻辑可参考后续方案优化 useEffect(() => { const statusInterval = setInterval(() => { getData(); }, 10000); return () => clearInterval(statusInterval); }, []); // 列表渲染逻辑略 }
优于setInterval短轮询的数据获取方案
按推荐优先级从高到低排列:
- WebSocket 实时推送:优先选择Laravel框架原生提供的事件广播能力,搭配WebSocket服务即可实现后端主动向前端推送新通知。新投诉产生时后端直接触发事件推送到前端,前端无需定时发请求,实时性最高,也不会产生无效请求。前端订阅对应频道后,收到推送事件时直接更新列表、触发提示音即可,完全替代轮询逻辑。
- 长轮询(Long Polling):如果暂时不具备搭建WebSocket服务的条件,可以将普通短轮询改成长轮询:前端发起请求后,后端如果没有新数据就暂时挂起连接不返回,直到有新数据或者连接超时才响应,前端收到响应后立刻发起下一次请求。相比固定间隔的setInterval轮询,长轮询的无效请求占比极低,实时性也更接近WebSocket。
- 优化版轮询:如果必须使用普通轮询,需要修正现有代码的缺陷:当前写法无论上一次请求是否完成,到固定间隔就会发起新请求,网络较差时会出现请求堆叠、数据竞态问题。建议改成递归setTimeout的实现,等上一次请求完全结束后再等待间隔时间发起下一次请求,同时增加页面可见性判断,用户切到其他标签页时暂停轮询,切回页面时立刻拉取最新数据并恢复轮询,减少无效资源消耗。优化后的轮询参考代码:
useEffect(() => { let timer = null; let isPageVisible = true; const poll = async () => { if (!isPageVisible) return; await getData(); timer = setTimeout(poll, 10000); }; // 页面可见性变化监听 const visibilityChangeHandler = () => { isPageVisible = !document.hidden; if (isPageVisible && !timer) { getData(); timer = setTimeout(poll, 10000); } else if (!isPageVisible && timer) { clearTimeout(timer); timer = null; } }; document.addEventListener('visibilitychange', visibilityChangeHandler); // 组件挂载时立刻拉取一次数据 poll(); return () => { clearTimeout(timer); document.removeEventListener('visibilitychange', visibilityChangeHandler); }; }, []);
内容的提问来源于stack exchange,提问作者syrine
相关产品推荐
相关产品推荐

