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
相关产品推荐
相关产品推荐

