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

React:如何在useEffect内终止playKeyboard函数的前一次调用?

解决useEffect中playKeyboard实例重复创建的终止重置问题

我懂你现在的痛点:每次useEffect触发调用playKeyboard时,都会生成新的AudioSynth实例,之前的播放状态没法彻底终止,导致多个实例同时运行。这里给你一套实际可用的解决方案,核心是用useRef追踪并管理旧实例和音频对象,实现完全的状态重置:

第一步:用useRef存储实例与音频容器

在你的组件里添加两个useRef,用来保存当前的AudioSynth实例和所有正在播放的Audio容器——useRef的.current属性不会触发组件重渲染,非常适合存储这类可变状态:

import { useEffect, useState, useRef } from 'react';

// ... 其他组件代码

const audioSynthRef = useRef(null);
const audioContainersRef = useRef([]);

第二步:修改playKeyboard,先清理再创建新实例

在playKeyboard函数开头,先终止之前所有的播放并清理资源,再创建新的AudioSynth实例。这样每次调用时都会先重置状态:

async function playKeyboard(keyboardArray, pause, index, selectSound) {
  // 1. 终止并清理之前的所有音频
  if (audioContainersRef.current.length > 0) {
    audioContainersRef.current.forEach(audio => {
      if (audio) {
        audio.pause(); // 暂停播放
        audio.src = ''; // 释放音频资源
      }
    });
    audioContainersRef.current = []; // 清空容器数组
  }

  // 2. 重置之前的AudioSynth实例(如果有的话)
  if (audioSynthRef.current) {
    // 如果AudioSynth提供了销毁/重置方法,优先调用库方法
    // 比如 audioSynthRef.current.reset();
    // 没有的话直接置空即可
    audioSynthRef.current = null;
  }

  // 3. 创建新的实例并保存到ref
  audioSynthRef.current = new AudioSynth();
  audioSynthRef.current.setVolume(0.08);
  const __octave = 4;

  // 4. 修改fnPlayNote,把音频容器存入ref数组
  const fnPlayNote = function(note, octave, duration) {
    const src = audioSynthRef.current.generate(selectSound.value, note, octave, duration);
    const container = new Audio(src);
    audioContainersRef.current.push(container); // 存入数组便于后续清理

    container.addEventListener("ended", function() {
      // 播放结束后从数组中移除,避免内存泄漏
      const idx = audioContainersRef.current.indexOf(container);
      if (idx !== -1) audioContainersRef.current.splice(idx, 1);
      container = null;
    });

    container.addEventListener("loadeddata", function(e) {
      // 只有非暂停状态才播放
      if (!pause) {
        e.target.play();
      }
    });
  }

  // ... 你的其他播放逻辑代码
}

第三步:给useEffect添加清理函数

在useEffect的返回函数中加入清理逻辑——这样当组件卸载、或者keyboard/stat变化时,会自动终止之前的播放,确保每次effect触发前都是干净的状态:

useEffect(() => {
  if (keyboard.length > 0) {
    playKeyboard(keyboard, stat.pause, stat.index, stat.selectSound);
  }

  // 清理函数:依赖变化或组件卸载时执行
  return () => {
    // 终止所有音频
    if (audioContainersRef.current.length > 0) {
      audioContainersRef.current.forEach(audio => {
        if (audio) {
          audio.pause();
          audio.src = '';
        }
      });
      audioContainersRef.current = [];
    }
    // 重置synth实例
    audioSynthRef.current = null;
  };
}, [keyboard, stat]);

乐器切换的适配

你现有的onInstrumentChange逻辑已经能触发stat更新,进而触发useEffect的清理函数——所以每次切换乐器时,都会自动终止之前的播放,创建新的AudioSynth实例,完全符合你的需求。

额外说明

如果AudioSynth库本身提供了专门的终止/重置方法,优先用库的方法会更可靠;如果没有,我们上面通过管理Audio实例的方式已经能实现彻底的状态重置,同时避免内存泄漏。

内容的提问来源于stack exchange,提问作者Ged

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 19:17:34