React & State 创建计时器

问题描述 投票:0回答:1

我仅使用 React & State 制作计时器,会话时间设置为 2 分钟,休息时间设置为 1 分钟。当点击播放按钮时,计时器将开始递减数值,当会话时间变为0时,它将移至休息时间,当休息时间变为0时,它将移至会话时间,直到按下停止按钮或重置。

这里我面临两个问题:

  1. 第一个周期,当会话时间变为0时,进入休息时间。但由于休息时间设置为1分钟,所以不是从规定时间开始。第一个周期后,它按预期工作。
  2. 当我重复或两次单击“播放”按钮时,我无法暂停计时器。

请从CodePen

找到我的完整代码
  handleTimer = () => {
      this.interval = setInterval(()=>{
        
        if(this.state.sessionMin == 0 && this.state.Sec == 0){
          clearInterval(this.interval);
          this.setState({
            sessionMin: this.state.sessionConst
          })
       
          this.breakInterval = setInterval(()=>{
            if(this.state.breakMin == 0 && this.state.Sec == 0){
              clearInterval(this.breakInterval);
              this.setState({
                breakMin: this.state.breakConst
                
              })
              console.log(this.state.breakMin)
              this.handleTimer()
            }
            
        
        this.setState({
        display: 'Break',
        Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
        breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
      })
            
          },1000)
        }
        
        this.setState({
        display: 'Session',
        Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
        sessionMin: this.state.Sec == 0 ? this.state.sessionMin-1 : this.state.sessionMin
      })
      
    },1000)
  }
  

注意:第二次设置为 10 秒,而不是 60 秒以进行快速测试。 注意:重置按钮尚未起作用。 注意:当停止按钮后按下播放按钮时,应该从该状态继续,而不是重新开始。

我使用“状态”来获取最新或最近的数据,但它不起作用。我不想尝试钩子或任何其他东西,只想使用简单的组件级状态。

javascript reactjs timer setinterval
1个回答
0
投票

如果您想跳过解释并转到解决方案,请向下滚动。

  1. 假设您的会话计时器达到状态会话:0分0秒。看下面的代码(带注释):

     // current state: *Session: 0min 0sec*
     this.interval = setInterval(()=>{
       if (this.state.sessionMin == 0 && this.state.Sec == 0) {
       // You tell a browser API "Stop calling this interval function in 
       // future", but execution of current function call continues !!!
       clearInterval(this.interval)
    
       // Execution continues, right? So, this.setState.sessionMin 
       // takes value of this.state.sessionConst, that is 2
       this.setState({ sessionMin: this.state.sessionConst })
    
       // Here you are setting breakInterval, but runtime doesn't care
       // about it for now, because first call of this.breakInterval 
       // will be made after 1000ms
       // (setInterval(() => {}, 1000) 
       // ...wait 1000ms ... 
       // first call 
       // ... wait 1000ns 
       // second call
       // ... etc 
       this.breakInterval = setInterval(()=>{
         // ...breakInterval logic
       },1000)
     }
    
     this.setState({
       display: 'Session',
       // this.state.Sec == 0, so it's value is set to 10
       Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
       // As mentioned above, this.state.sessionMin is 2,
       // so here this.state.sessionMin is set to 1 (2 - 1)
       sessionMin: this.state.Sec == 0 ? this.state.sessionMin-1 : this.state.sessionMin
     })
    },1000)
    

因此,执行上述代码后,计时器的状态是 Session: 1min 10sec,而不是您预期的 Break: 0min 10sec。 然后1s后breakInterval被调用,看下面的代码:

    // current state: *Session: 1min 10sec*
    this.breakInterval = setInterval(()=>{
      // No, condition is not satisfied, because current 
      // state is *Session: 1min 10sec*
      if(this.state.breakMin == 0 && this.state.Sec == 0){
        // ...clear breakInterval logic 
      }

      this.setState({
         // this.state.display will be 'Break'
        display: 'Break',
        // and this.state.Sec will be 9
        Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
        // As mentioned above, current state is still **Session: 1min 
        // 10sec**, i.e. this.state.Sec is 10. So in 
        // ternary operator SECOND expression will be executed
        // breakMin: this.state.breakMin
        breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
      })

    },1000)

breakMin 在构造函数中初始化为 1

  this.state={
      display: 'Session',
      sessionMin: 2,
      sessionConst:2,
      // Yep, here
      breakMin: 1,
      breakConst: 1,
      Sec: 0,
    }

瞧!在会话:1分10秒之后,您的计时器状态现在为休息:1分9秒。这里有几个问题:

  • 会话间隔后 this.state.display 值仍然是 Session
  • 您提出的问题:breakInterval开始时的分钟数不正确

为什么在第一个周期计时器状态Session:1min 10sec更改为Break:0min 9sec,即分钟数是正确的?坦白说,不正确。 因此,在 Break: 1min 9sec 计时器达到Break: 0min 0sec 后。再次看下面的代码:

    // current state: Break: 0min 0sec**
    this.breakInterval = setInterval(()=>{
      // Yes, condition is satisfied
      if(this.state.breakMin == 0 && this.state.Sec == 0){
        // You tell a browser API "Stop calling this function in 
        // future", but current function execution continues !!!
        clearInterval(this.breakInterval)
        // this.state.breakMin is now 1
        this.setState({ breakMin: this.state.breakConst })
        console.log(this.state.breakMin)
        // this.handleTimer() is called, but this.interval()
        // will be called after 1000ms
        this.handleTimer()
      }

      this.setState({
        // this.state.display will be 'Break'
        display: 'Break',
        // this.state.Sec will be 10
        Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
        // this.state.Sec is still 0, so in ternary operator 
        // FIRST expression will be executed
        // breakMin: this.state.breakMin - 1
        // Because current value of this.state.breakMin is 1,
        // result value will be 0 (1 - 1) !!!!!!!
        breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
      })

    },1000)

执行上述代码后,this.state.breakMin的实际值将是0,而不是1,当前状态将是Break:0min 10sec。之后,将调用会话间隔,状态将是Session:1min 9sec,循环将再次开始。 假设会话间隔达到Session: 0min 0sec。您还记得,之后计时器状态将为Session:1min 10sec(请参阅第一个代码示例)。所以:

    // **Session: 1min 10sec**
    this.breakInterval = setInterval(()=>{
      // No, condition is not satisfied
      if(this.state.breakMin == 0 && this.state.Sec == 0){
        // ...clear breakInterval logic 
      }

      this.setState({
        display: 'Break',
        // this.state.Sec will be 9
        Sec: this.state.Sec == 0 ? 10 : this.state.Sec-1,
        // this.state.Sec is 10, so in 
        // ternary operator SECOND expression will be executed
        // breakMin: this.state.breakMin
        // Beacause actual value of this.state.breakMin is 0,
        // new breakMin value will be zero too
        breakMin: this.state.Sec == 0 ? this.state.breakMin-1 : this.state.breakMin
      })

    },1000)

因此,在第一个周期之后,分钟数是正确的(尽管内部的所有内容都不能正常工作)

  1. 当您第一次单击后单击“播放”按钮时,您将调用 this.handleTimer 方法并创建第二个计时器。然后,当您单击“停止”按钮时,您清除了第二个计时器,但第一个计时器仍在工作!并且您无法停止第一个计时器,因为 this.interval 变量已被新的计时器引用覆盖。

所以,解决方案:

class TypesOfFood extends React.Component {
  constructor(props) {
    super(props)
    this.state={
      display: 'Session',
      paused: false,
      sec: 0,
      min: this.sessionMinutesConst,
    }
  }
  
  // use 9, not 10 for tests or 59, not 60 in "production"
  // it will be look like:
  // 2:01 -> 2:00 -> 1:59
  secondsInOneMinute = 9
  sessionMinutesConst = 2
  breakMinutesConst = 1
  
  timer = null
  
  decrementTimer = () => {
     this.setState(currentState => ({
        sec: currentState.sec === 0 ? this.secondsInOneMinute : currentState.sec - 1,
        min: currentState.sec === 0 ? currentState.min - 1 : currentState.min
      }))
  }
  
  createTimer = () => { 
    this.timer = setInterval(() => {
      this.handleTimer()
      this.decrementTimer()
    }, 1000)
  }
  
  handleTimer = () => {    
    const { sec, min } = this.state
    const isSessionTimer = this.state.display === 'Session'
    
    if (sec === 0 && min === 0) {
       this.setState({
         display: isSessionTimer ? 'Break' : 'Session',
         min: isSessionTimer ? this.breakMinutesConst : this.sessionMinutesConst
       })
    }
  }

  handleStop = () => {
    this.setState({ paused: true })
    clearInterval(this.timer)
  }
  
  handleReset = () => {
    clearInterval(this.timer)
    this.timer = null
    
    this.setState({
      display: 'Session',
      paused: false,
      sec: 0,
      min: this.sessionMinutesConst,
    })
  }
  
  handlePlay = () => {
    if (this.timer && !this.state.paused) return
    
    this.createTimer()
    this.setState({ paused: false })
  }

  render() {
    return (
      <div id="container">
        <h3>{this.state.display}</h3>
        <h1>{this.state.min}m:{this.state.sec}s</h1>
        <button onClick={this.handlePlay}>Play</button>
        <button onClick={this.handleStop}>Stop</button>
        <button onClick={this.handleReset}>Reset</button>
      </div>
    )
  }
}

需要注意的事项:

  • 我只用一个计时器替换了两个计时器。当计时器状态为 [当前计时器]: 0min 0sec时,this.state.displaythis.state.min 更改为新值 - Session 或 Break 以及 this.sessionMinutesConst 或 this.breakMinutesConst 分别在 handleTimer 中方法。
  • 类中的
  • timer属性。当创建计时器时,它的引用被分配给这个属性,因此我们可以随时轻松地清除计时器
  • handleStop方法。我向状态添加了 paused 属性,因此很容易理解计时器何时停止以及何时完全清除
  • handlePlay方法。仅当没有当前计时器或有当前计时器时才会创建新计时器,但只是暂停。
  • 我删除了构造函数中的 binds - 将方法声明为箭头函数时没有必要
  • 我实现了handleReset方法

希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.