0

我是一个初学者,我觉得我在某个地方犯了一个根本性的错误。我正在制作一个简单的 React 组件,以便使用 Tone JS 按顺序播放音符。我无法使用按钮更新笔记。当我单击按钮检查它们是否已更新时,状态似乎已更改,但该repeat功能仍在播放“旧音符”。我哪里错了?

// create a synth
let synth = new Tone.Synth({
  attack: 0.5,
  decay: 0.5,
  sustain: 1,
  release: 5,
  volume: -10
}).toDestination()

const Main = () => {

  // set state with note values
  const [noteValue, setNoteValue] = useState([
    { note: 'C4', properties: ''},
    { note: 'C4', properties: ''}
  ])
  const [isPlaying, setIsPlaying] = useState(false)

  // start/stop transport
  const startStop = () => {
    if (!isPlaying) Tone.start()
    setIsPlaying(!isPlaying)
    !isPlaying ? Tone.Transport.start() : Tone.Transport.stop()
  }

  Tone.Transport.bpm.value = 140

  // song loop function - always displays the same notes after state change
  let index = 0
  function repeat(time){
    const position = index % noteValue.length
    const synthNote = noteValue[position]
    console.log(noteValue)
    synth.triggerAttackRelease(synthNote.note, time)
    index++
  }

  // set the transport on the first render
  useEffect(() => {
    Tone.Transport.scheduleRepeat((time) => { 
      repeat(time)
    }, '4n')
  },[])

// the "change note values" button doesn't change them inside the repeat function
return <>

    <button onClick={() => setNoteValue([
    { note: 'C5', properties: ''},
    { note: 'C5', properties: ''}
  ])}>Change Note Values</button>

    <button onClick={() => console.log(noteValue)}>Check note values</button>

    <button onClick={() => startStop()}>Start/Stop</button>
</>

谢谢你。

4

1 回答 1

1

useEffect在第一次渲染时执行一次。在那里useEffect,您安排重复引用该repeat范围内的函数的函数。该repeat函数引用noteValue,它又是一个名为的变量的值,该变量noteValue存在于第一个渲染的范围内。

您的反应状态值实际上确实发生了变化,但是由于您始终只引用效果中第一次执行范围内的变量,因此您不会遇到任何这种情况。

要查看它实际上正在发生变化,您可以console.log(noteValue)在代码中的某个位置添加一个。

要解决这个问题,您确实需要了解范围和闭包的概念。我建议阅读https://whatthefuck.is/closure

可能的解决方案:

一种可能的解决方案是scheduleRepeat返回一个unschedule方法(无论如何您都需要它,否则在卸载组件后声音将继续播放)。在这种情况下:

  useEffect(() => {
    function repeat(time){
      const position = index % noteValue.length
      const synthNote = noteValue[position]
      console.log(noteValue)
      synth.triggerAttackRelease(synthNote.note, time)
      index++
    }

    const unschedule = Tone.Transport.scheduleRepeat((time) => { 
      repeat(time)
    }, '4n')

    return unschedule;
  },[noteValue])

简而言之:repeat被移动到 中useEffectuseEffect获得依赖noteValue并返回一个清理回调。useCallback否则,您repeat将需要一个noteValue作为依赖项并添加repeatuseEffect. 这样,两者合二为一。

于 2021-04-10T11:18:13.187 回答