React音量滑块点击生效但键盘触发播放音量未同步问题
问题根因
这个问题本质是React Hooks的闭包陈旧值问题:
- DrumPad组件内绑定键盘keydown事件的useEffect依赖数组写的是空
[],只会在组件首次挂载时执行一次,最终绑定到事件上的handleKeyPress、以及它内部调用的playSound,全都是组件首次渲染时生成的版本。这个旧版本函数闭包捕获的volumeLevel永远是初始值0.5,根本读不到滑块调整后的新音量值。 - 而鼠标onClick触发的
playSound,是每次组件重渲染(音量变化会触发父组件App重渲染,DrumPad收到新props后也会重渲染)时新生成的版本,能拿到最新的volumeLevel值,所以点击播放时音量是正常同步的。
修复方案
二选一即可:
方案1:补全useEffect依赖项
给useEffect加上所有用到的响应式依赖,每次依赖变化时自动解绑旧事件、绑定能拿到最新值的新处理函数:
useEffect(() => { // 把handleKeyPress移到effect内部,避免漏写依赖 const handleKeyPress = (e) => { if(e.keyCode === pad.keyCode){ playSound(); } } document.addEventListener('keydown', handleKeyPress); return () => { document.removeEventListener('keydown', handleKeyPress) }; // 把用到的外部值全加到依赖数组里 }, [pad.keyCode, playSound]);
如果不想每次渲染都重新创建playSound导致事件频繁重绑,可以用useCallback包裹playSound:
const playSound = useCallback(() => { const audioTag = document.getElementById(pad.letter); audioTag.volume = volumeLevel; audioTag.play(); setSoundText(pad.id) }, [pad.letter, pad.id, volumeLevel, setSoundText])
方案2:用useRef存最新音量值
如果不想频繁解绑重绑键盘事件,可以用useRef存最新的音量值,事件触发时直接从ref读取,不受闭包限制:
function DrumPad({ pad, setSoundText, volumeLevel }) { // 建一个ref存实时音量 const latestVolume = useRef(volumeLevel); // 每次音量更新时同步给ref useEffect(() => { latestVolume.current = volumeLevel; }, [volumeLevel]) // 这个effect只需要在挂载时执行一次,不用改依赖 useEffect(() => { const handleKeyPress = (e) => { if(e.keyCode === pad.keyCode){ playSound(); } } document.addEventListener('keydown', handleKeyPress); return () => { document.removeEventListener('keydown', handleKeyPress) }; }, []); const playSound = () => { const audioTag = document.getElementById(pad.letter); // 从ref读最新的音量值 audioTag.volume = latestVolume.current; audioTag.play(); setSoundText(pad.id) } return ( <div className="drum-pad" id={pad.id} onClick={playSound}> <p>{pad.letter}</p> <audio src={pad.src} className="clip" id={pad.letter}> </audio> </div> ) }
内容的提问来源于stack exchange,提问作者Ian
相关产品推荐
相关产品推荐

