Javascript Can';无法获取要更新的反应状态

Javascript Can';无法获取要更新的反应状态,javascript,reactjs,state,tonejs,Javascript,Reactjs,State,Tonejs,我是个初学者,我觉得我在某个地方犯了一个根本性的错误。我正在制作一个简单的React组件,以便使用Tone JS按顺序播放音符。我不能用按钮更新笔记。当我点击一个按钮检查它们是否更新时,状态似乎已经改变,但repeat功能仍然播放“旧笔记”。我哪里做错了 // create a synth let synth = new Tone.Synth({ attack: 0.5, decay: 0.5, sustain: 1, release: 5, volume: -10 }).t

我是个初学者,我觉得我在某个地方犯了一个根本性的错误。我正在制作一个简单的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>
</>
//创建一个synth
让synth=new Tone.synth({
攻击:0.5,
衰减:0.5,
维持:1,,
发布日期:5,
卷:-10
}).toDestination()
常量Main=()=>{
//使用注释值设置状态
常量[noteValue,setNoteValue]=useState([
{注意:'C4',属性:'},
{注意:'C4',属性:'}
])
const[isplay,setisplay]=useState(false)
//启动/停止运输
常量startStop=()=>{
如果(!isplay)音调.start()
设置显示(!显示)
!isPlaying?Tone.Transport.start():Tone.Transport.stop()
}
Tone.Transport.bpm.value=140
//歌曲循环功能-状态更改后始终显示相同的音符
设指数=0
功能重复(时间){
常量位置=索引%noteValue.length
const syntnote=noteValue[位置]
console.log(noteValue)
synth.triggeratackrelease(synthNote.note,time)
索引++
}
//在第一次渲染时设置传输
useffect(()=>{
Tone.Transport.scheduleRepeat((时间)=>{
重复(时间)
},'4n')
},[])
//“更改注释值”按钮不会在repeat函数中更改它们
返回
setNoteValue([
{注意:'C5',属性:'},
{注意:'C5',属性:'}
])}>更改注释值
console.log(noteValue)}>检查注释值
startStop()}>开始/停止

谢谢。

您的
useffect
在第一次渲染时执行一次。在该
useffect
中,您计划从该范围引用
repeat
函数的函数的重复。该
repeat
函数引用
noteValue
,这也是名为
noteValue
的变量的值,该变量存在于第一次渲染的范围内

您的react state值实际上会发生变化,但由于您始终只引用effect中第一次执行的范围中的变量,因此您不会遇到任何这种情况

要查看它是否在实际更改,可以在代码中的某个位置添加
console.log(noteValue)

要解决这个问题,您确实需要理解范围和闭包的概念。我推荐阅读

可能的解决办法:

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

useffect(()=>{
功能重复(时间){
常量位置=索引%noteValue.length
const syntnote=noteValue[位置]
console.log(noteValue)
synth.triggeratackrelease(synthNote.note,time)
索引++
}
const unschedule=Tone.Transport.scheduleRepeat((时间)=>{
重复(时间)
},'4n')
非计划返回;
},[noteValue])
简而言之:
repeat
被移动到
useffect
中,
useffect
获取对
noteValue
的依赖项并返回清理回调。
否则,您将需要一个
useCallback
环绕
repeat
noteValue
作为依赖项,并添加
repeat
作为
useffect
的依赖项。通过这种方式,这两个功能集于一身。

谢谢您的帮助。我读了你说的话,但我还是很困惑。我试着将函数中的变量移出它,但没有成功。我还注意到,当我从
useffect
中删除第二个参数时,我可以将音符更改为播放,因此我也被这一点弄糊涂了。但是,删除此参数会导致
Tone.transport
运行大量的时间,并导致播放大量的音符。将第二个参数更改为useEffect将导致它在其中一个依赖项更改时重新运行。但是,您也可以在重新运行效果之前进行清理,您需要在其中停止旧的运行效果。阅读推荐(当然除了react文档):谢谢你,弗瑞。我已经阅读了所有这些材料和React文档,感觉我已经尝试了所有的方法,但不幸的是,我不知道如何让它发挥作用。我在上面添加了一个示例解决方案。非常感谢您的解决方案。我认为这是有道理的(我确信这是有道理的,但我是一个初学者),但现在当我改变音符状态时,序列会在原始的基础上重复。你知道这是为什么吗?你能建议我如何解决这个问题吗?再次感谢您抽出时间