useState 保持之前的状态

useState keeps the previous state

在我的应用程序中,我点击 Play 按钮,音频应该开始播放,当它结束时语音识别应该开始收听,当它结束时应该在控制台中打印消息(它将更改为一些其他操作进一步)。

如果我单击 stop 按钮,我的应用程序还具有停止识别过程的功能,为此我使用 useState,其中 false 状态更改为 true

但是,我遇到了一个问题:当我尝试通过单击 Stop 按钮停止收听时,状态变为 true 并且它应该在此处停止,但尽管如此我有 if(isPlaying === false) 条件,在 true 之后,作为最后一个操作,我在控制台中得到 false ‍♂️ 为什么会这样,如何解决?

const [song] = useState(typeof Audio !== 'undefined' && new Audio())
  const [isPlaying, setIsPlaying] = useState(false)

  function playSong() {
    setIsPlaying(!isPlaying)
    if (listening) {
      SpeechRecognition.stopListening()
    }
    if (isPlaying === false) {
      song.src = 'https://raw.songToPlay.mp3'
      song.play()
      console.log('Song is playing: ' + isPlaying)
      song.addEventListener('ended', () => {
        console.log('Recognition is started: ' + isPlaying)
        SpeechRecognition.startListening()
      })
      recognition.addEventListener('audioend', () => {
        console.log('Recognition is finished: ' + isPlaying)
      })
    } else {
      console.log('When stop is clicked: ' + isPlaying)
    }
  }

  return (
    <div>
      <p>Microphone: {listening ? 'on' : 'off'}</p>
      <button onClick={playSong}>{isPlaying ? 'Stop' : 'Play'}</button>
      <p>{transcript}</p>
    </div>
  )

输出:

从 playSong 中记录状态将在定义函数时记录状态。这意味着该值将过时,即陈旧。这是因为称为闭包的功能概念。来自 Wikipedia “与普通函数不同,闭包允许函数通过闭包的值或引用的副本访问那些捕获的变量,即使函数是在其范围之外调用的。”

那么当状态改变时,我们应该如何记录当前状态呢?通过使用 useEffect 钩子。每当任何依赖项发生变化时,此函数都会 re-run。

您还可以 return 一个 'cleanup' 函数来删除侦听器等

CodeSandbox

  const [isPlaying, setIsPlaying] = useState(false);

  useEffect(() => {
    if (!isPlaying) {
      console.log("Starting to play");
      // add your event listener here to wait for song to end and start speech recognition
    } else {
      console.log("When stop is clicked: " + isPlaying);
      // stop your speech recognition here
    }

    return () => {
      // this is a cleanup function - you can use it to remove event listeners so you don't add them twice
    };
  }, [isPlaying]);

  return (
    <div>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? "Stop" : "Play"}
      </button>
    </div>
  );