我有以下使用wavesurfer.js 的功能React组件
import * as React from "react"
import * as WaveSurfer from 'wavesurfer.js'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlayCircle, faPauseCircle } from '@fortawesome/free-regular-svg-icons'
import { IconDefinition } from '@fortawesome/fontawesome-svg-core'
interface WaveformPlayerProps {
audioUrl: string
}
const WaveformPlayer = (props: WaveformPlayerProps) => {
const [playing, setPlaying] = React.useState(false)
const [audioContext, setAudioContext] = React.useState(new AudioContext())
const [waveform, setWaveform] = React.useState(null)
const waveformRef = React.useRef();
React.useEffect(() => {
if (waveformRef.current) {
const wavesurfer = WaveSurfer.create({
audioContext: audioContext,
container: waveformRef.current
});
wavesurfer.on('finish', togglePlayPause)
wavesurfer.load(props.audioUrl)
setWaveform(wavesurfer)
}
}, [])
function togglePlayPause(): void {
if (audioContext.state == "suspended") {
audioContext.resume()
}
waveform.playPause()
setPlaying(!playing)
}
function stateButtonIcon(): IconDefinition {
if (playing) {
return faPauseCircle
} else {
return faPlayCircle
}
}
return (
<div>
<button id="waveformAudioControl" className="button is-large" onClick={togglePlayPause}>
<FontAwesomeIcon icon={stateButtonIcon()} />
</button>
<div ref={waveformRef}>
</div>
</div>
)
}
export default WaveformPlayer
我认为我可以访问useEffect
之外的WaveSurfer对象的唯一方法是使用useState
,但这对我来说并不合适,因为如果组件中的某些状态发生变化(例如,用户按下播放/暂停按钮(,WaveSurver组件就不应该完全重新渲染。这让我觉得我的方法不正确?
编辑:忘记提到waveform.playPause()
失败,因为点击播放按钮时为空。
import React, { Fragment, useState, useEffect } from 'react'
import WaveSurfer from 'wavesurfer.js'
import audioFile from './../assets/sample-wave-file-5MB.wav'
const WaveSurferStatelessForm = () => {
let [isPlaying, setIsPlaying] = useState(false)
let [waveSurfer, setWaveSurfer] = useState(null)
useEffect(() => {
setWaveSurfer(WaveSurfer.create({
container: '#waveform'
}))
}, [])
useEffect(() => {
if(waveSurfer) {
waveSurfer.load(audioFile)
}
}, [waveSurfer])
const togglePlayPause = () => {
waveSurfer.playPause()
setIsPlaying(!isPlaying)
}
return (
<Fragment>
<div id="waveform" ></div>
<button onClick={() => togglePlayPause()}>{isPlaying ? '||' : '+'}</button>
</Fragment>
)
}
export default WaveSurferStatelessForm
单击播放按钮后,waveform.playPause()
将失败,因为您正在更新状态playing
,这将导致组件重新渲染。由于useEffect仅在第一次渲染时运行,因此一旦状态更改,波形将设置为null,并且在组件重新渲染时useEffect不会重新实例化它。要解决此问题,您需要将波形设置为state,使其始终实例化,或者在[]
中添加playing
变量,使useEffect在每次组件重新渲染时重新实例化波形。我上面的解决方案使用了前者,并在下面进行了详细解释。
在第一次渲染后设置waveSurfer对象,这样<div id="waveform" ></div>
将在创建waveSurfer对象之前安装到DOM。
useEffect(() => {
setWaveSurfer(WaveSurfer.create({
container: '#waveform'
}))
}, [])
现在已经设置了waveSurfer对象,我们需要加载音频。此useEffect
将运行两次,但仅在设置waveSurfer对象后加载音频,这将是最后一次调用此useEffect
。在此之前,waveSurfer对象为null。
useEffect(() => {
if(waveSurfer) {
waveSurfer.load(audioFile)
}
}, [waveSurfer])
注意,useEffect
将始终在第一次渲染之后运行,然后在传递给[]
的任何状态更改之后运行。如果没有[]
,它将在每次渲染后运行。因此,您不需要检查div是否存在if (waveformRef.current)
,它将始终存在,因为useEffect
在第一次渲染后执行。在状态满或class
组件中,这类似于componentDidMount
我能想到的最好的方法是声明const waveform = React.useRef<WaveSurfer | null>(null)
并在文件的其余部分使用waveform.current
。