简体   繁体   English

反应setState不更新状态; 回调未被调用

[英]React setState not updating state; callback not being called

I'm trying to take the metronome I built following the "Tale of Two Clocks" tutorial and convert it into a simple React project. 我正在尝试按照“两个时钟的故事”教程构建的节拍器,并将其转换为简单的React项目。 I figured it would be trivially easy to map the metronome's parameters (tempo, note divisions, etc.) and expand on it to build out a step sequencer (hooray for techno!). 我认为映射节拍器的参数(速度,音符划分等)并对其进行扩展以构建步进音序器(对技术而言万岁!)非常容易。 That said, I'm having trouble with a specific piece and I'm wondering why. 也就是说,我在处理特定作品时遇到麻烦,我想知道为什么。

I've got pretty much all the salient pieces as state on the Metronome component, like so: 我在Metronome组件上几乎拥有所有重要的部分,例如:

import React from 'react'

class Metronome extends React.Component {
  constructor (props) {
    super(props)

    this.audioContext = new (window.AudioContext || window.webkitAudioContext)()

    this.state = {
      tempo: 120.0,
      noteResolution: 0,
      isPlaying: false,
      current16thNote: 0,
      gateLength: 0.05,
      noteLength: 0.25,
      nextNoteTime: 0.0
    }

    this.lookahead = 0.1
    this.schedulerInterval = 25.0
    this.timerID = null

    this.updateStartStopText = this.updateStartStopText.bind(this)
    this.play = this.play.bind(this)
    this.scheduler = this.scheduler.bind(this)
    this.scheduleNote = this.scheduleNote.bind(this)
    this.nextNote = this.nextNote.bind(this)
  }

  updateStartStopText () {
    return this.state.isPlaying ? 'Stop' : 'Start'
  }

  play () {
    this.setState({ isPlaying: !this.state.isPlaying }, () => {
      if (this.state.isPlaying) {
        this.setState({ current16thNote: 0 }, () => {
          this.setState({
            nextNoteTime: this.audioContext.currentTime
          }, this.scheduler)
        })
      } else {
        window.clearTimeout(this.timerID)
      }
    })
  }

  scheduler () {
    while (this.state.nextNoteTime < this.audioContext.currentTime + this.lookahead) {
      this.scheduleNote(this.state.current16thNote, this.state.nextNoteTime)
      this.nextNote()
    }

    this.timerID = window.setTimeout(this.scheduler, this.schedulerInterval)
  }

  scheduleNote (beatNumber, time) {
    if ((this.state.noteResolution === 1) && (beatNumber % 2)) {
      return
    } else if ((this.state.noteResolution === 2) && (beatNumber % 4)) {
      return
    }
    const osc = this.audioContext.createOscillator()
    osc.connect(this.audioContext.destination)
    if (!(beatNumber % 16)) {
      osc.frequency.value = 220
    } else if (beatNumber % 4) {
      osc.frequency.value = 440
    } else {
      osc.frequency.value = 880
    }

    osc.start(time)
    osc.stop(time + this.state.gateLength)
  }

  nextNote () {
    var secondsPerBeat = 60.0 / this.state.tempo
    let nextTime = this.state.nextNoteTime + (secondsPerBeat * this.state.noteLength)

    this.setState({
      nextNoteTime: nextTime,
      current16thNote: (this.state.current16thNote + 1)
    }, () => {
      if (this.state.current16thNote === 16) {
        this.setState({ current16thNote: 0 })
      }
    })
  }

  render () {
    return (
      <div>
        Metronome

        <button type='button' onClick={this.play}>{this.updateStartStopText()}</button>
      </div>
    )
  }
}

export default Metronome

More or less everything plays out like you'd expect from React, swapping out vanilla JS variable reassignment/incrementing with this.setState() calls, . 或多或少的一切都会像React所期望的那样进行,用this.setState()调用替换this.setState() JS变量重新分配/递增。 There are quite a few situations where I need to do calculations with an updated value in state and, knowing that setState() is asynchronous and often batches calls, I rely somewhat heavily on the optional callback that setState takes. 在很多情况下,我需要使用状态中的更新值进行计算,并且知道setState()是异步的并且经常分批调用,因此我在某种程度上依赖于setState接受的可选回调。

The one place where things seem to be going awry is in the nextNote() method. 事情似乎出现问题的一个地方是nextNote()方法。 The state never updates properly: this.state.nextNoteTime never gets an updated value beyond that which it is assigned in the play() method The code as it is will never get into the callback passed to setState() called from nextNote() . 状态永远不会正确更新: this.state.nextNoteTime永远不会获得超出play()方法中分配的值的更新值。代码本身永远不会进入传递给从nextNote()调用的setState()的回调中。 If I remove nextNoteTime from the component's state and save it as a property on the component, manually updating and tracking its value, the metronome seems to work as expected, à la the end state of my metronome in vanilla JS. 如果我从组件的状态中删除nextNoteTime并将其保存为组件上的属性,手动更新并跟踪其值,则节拍器似乎可以按预期工作,就像我在香草JS中的节拍器的最终状态一样。 Technically a success, but not the answer I'm looking for. 从技术上讲是成功的,但不是我要寻找的答案。

I tried using Promises and I tried using the React Component API ( componentWillUpdate , shouldComponentUpdate , etc.) to figure out whether or not the next state is the one I'm looking for. 我尝试使用Promises并尝试使用React Component API( componentWillUpdateshouldComponentUpdate等)来确定下一个状态是否是我要寻找的状态。 But beyond a certain point (like 3 recursive calls of the scheduler() method), the while loop goes haywire since this.state.nextNoteTime never increases. 但是超出某个点(例如scheduler()方法的3次递归调用)之后,while循环变得很麻烦,因为this.state.nextNoteTime永远不会增加。 Gah! 加!

Thoughts? 有什么想法吗?

I think it's because you have nested setState s. 我认为这是因为您嵌套了setState

Try below as play method. 请尝试以下播放方法。

play() {
  const { isPlaying } = this.state;
  const newState = { isPlaying: !isPlaying };
  if (!isPlaying) {
    Object.assign(newState, {
      current16thNote: 0,
      nextNoteTime: this.audioContext.currentTime
    });
  }

  this.setState(newState, (...args) => {
    if (!isPlaying) {
      this.scheduler(...args);
    } else {
      window.clearTimeout(this.timerID)
    }
  });
}

Remove setState nesting for nextNote as well. nextNote删除nextNote setState嵌套。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM