You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 21:36:26