使用setInterval函数和React让Chrome冻结

时间:2017-06-28 13:11:58

标签: javascript reactjs

让我们看看是否有人可以帮助我解决问题倒计时的问题。我正在尝试设置分钟和秒点击+和 - 然后将秒传递给一个名为tottime的新变量。然后,当用户点击Go!时,应该触发倒计时但是......它没有发生,我不知道为什么Chome不断冻结。

您可以在此处查看整个项目https://github.com/staranco/react-scoreboard

下面是代码:

class Timer extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          secs: 0,
          mins: 0,
          tottime: 0
        };
    this.clickSum  = this.clickSum.bind(this);
    this.clickDif  = this.clickDif.bind(this);
    this.initCount  = this.initCount.bind(this);
  }

  clickSum() {
    if(this.state.secs < 59) {
      this.setState({secs: this.state.secs + 1});
    } else {
      this.setState({secs: 0});
      this.setState({mins: this.state.mins + 1});
    }
    this.setState({tottime: (this.state.secs + 1) + (this.state.mins * 60)});
    console.log(this.state.tottime)
  }

  clickDif() {
    if(this.state.secs > 0) {
     this.setState({secs: this.state.secs - 1});
     this.setState({tottime: (this.state.secs - 1) + (this.state.mins * 60)})
    } else if(this.state.mins > 0 && this.state.secs <= 0) {
      this.setState({mins: this.state.mins - 1});
      this.setState({secs: 59});
    }
  }

  initCount() {
    var self = this;
    while (self.state.tottime > 0) {
      setInterval(function() {
        console.log(self.state.tottime);
        self.setState({tottime: self.state.tottime - 1})
      }, 1000);
    }
  }

  render() {
    return (
      <div className="timer">
        <div className="timer_clock">
          <span>{this.state.mins}</span>:<span>{this.state.secs}</span>
        </div>

        <div className="timer_countdown"><span>{this.state.tottime}</span></div>

        <TimerBtn btnSymbol="+" onClick={this.clickSum} />
        <TimerBtn btnSymbol="-" onClick={this.clickDif} />

        <TimerBtn btnSymbol="Go!" onClick={this.initCount} />
      </div>
    );
  }
};

3 个答案:

答案 0 :(得分:1)

您的代码存在问题,

while(self.state.tottime > 0)

此条件始终为true,浏览器将继续设置setIntervals,并且不会执行setInterval

您可以按freeze chrome按钮:)进行验证。您可以看到我添加的console.log将被多次打印。

您可以将代码更改为这样的内容,

class Timer extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          secs: 0,
          mins: 0,
          tottime: 0
        };
    this.clickSum  = this.clickSum.bind(this);
    this.clickDif  = this.clickDif.bind(this);
    this.initCount  = this.initCount.bind(this);
    this.initCountOld = this.initCountOld.bind(this);
  }

  clickSum() {
    if(this.state.secs < 59) {
      this.setState({secs: this.state.secs + 1});
    } else {
      this.setState({secs: 0});
      this.setState({mins: this.state.mins + 1});
    }
    this.setState({tottime: (this.state.secs + 1) + (this.state.mins * 60)});
    console.log(this.state.tottime)
  }

  clickDif() {
    if(this.state.secs > 0) {
     this.setState({secs: this.state.secs - 1});
     this.setState({tottime: (this.state.secs - 1) + (this.state.mins * 60)})
    } else if(this.state.mins > 0 && this.state.secs <= 0) {
      this.setState({mins: this.state.mins - 1});
      this.setState({secs: 59});
    }
  }

  initCount() {
    var self = this;
    const refreshIntervalId = setInterval(()=>{
      self.setState({
        tottime: self.state.tottime - 1
      })
      if(this.state.tottime <= 0){ clearInterval(refreshIntervalId)}
  
    }, 1000)
  }
  initCountOld() {
   var self = this;
    while (self.state.tottime > 0) {
      console.log("setting infinite setintervals")
      setInterval(function() {
        console.log(self.state.tottime);
        self.setState({tottime: self.state.tottime - 1})
      }, 1000);
    }
  }

  render() {
    return (
      <div className="timer">
        <div className="timer_clock">
          <span>{this.state.mins}</span>:<span>{this.state.secs}</span>
        </div>

        <div className="timer_countdown"><span>{this.state.tottime}</span></div>

        <button btnSymbol="+" onClick={this.clickSum}>+</button>
        <button btnSymbol="-" onClick={this.clickDif}>-</button>

        <button btnSymbol="Go!" onClick={this.initCount}>Go</button>
        <button onClick={this.initCountOld}>Freeze chrome</button>
      </div>
    );
  }
};

ReactDOM.render(<Timer />,document.getElementById("app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

答案 1 :(得分:1)

问题在于您的initCount()方法。

  • while将无限执行 - 导致Chrome冻结,因为chorme将耗尽内存。

现在,你应该记住这个应用程序的两件事:

  1. 计时器何时开始 - &gt;当用户点击Go并且它应该开始间隔1000毫秒 - 你已经解决了这个问题。
  2. 计时器何时停止 - &gt;当倒计时最终达到0并且此时间隔应该被清除 - 你还没有解决这个问题。
  3. 因此,为了解决上述问题2,您应该做的是:

    1. 摆脱while循环
    2. setInterval()内添加条件语句,这将确定您的倒计时是否必须停止或是否必须继续。如果必须停止,则应清除与倒计时相关的间隔
    3. 现在如何清除间隔?继续阅读

      setInterval()会返回id,您可以将其保存在某个变量中,然后您可以使用clearInterval()并将id传递给它以清除间隔。

      例如:

      var id = setInterval(foo, 1000);
      clearInterval(id);
      

      所以你的initCount()必须是这样的(我已经注释掉了错误的代码):

        initCount() {
          var self = this;
          //while (self.state.tottime > 0) {
            self.intervalId = setInterval(function() {
              console.log(self.state.tottime);
              if(self.state.tottime > 0) {
                // Decrease tottime if its not zero
                self.setState({tottime: self.state.tottime - 1});
              } else {
                // Clear the timer if tottime == 0
                clearInterval(self.intervalId);
              }
            }, 1000);
         // }
        }
      

      以下是我使用您的github repo Countdown Sample

      中的代码创建的示例

      希望有所帮助:)

答案 2 :(得分:0)

这里的问题是setState()异步更新状态,这意味着您无法准确预测状态何时会更新。

查看setState的官方文档,here

  

setState()视为请求,而不是立即更新组件的命令。为了获得更好的感知性能,React可能会延迟它,然后在一次通过中更新几个组件。 React不保证立即应用状态更改。

     

setState()并不总是立即更新组件。它可以批量推迟更新或推迟更新。这使得在调用setState()潜在陷阱后立即阅读this.state。相反,请使用componentDidUpdatesetState callback (setState(updater, callback)),其中任何一个都可以保证在应用更新后触发。

问题

因此,您的代码中发生的事情是您选择了tottime的起始值,但是当您点击 Go!时,会发生以下情况:

  1. self.state.tottime > 0是真的。我们进入了while循环。
  2. 启动一个带有回调的间隔,在1秒内执行。 while循环不等待回调完成,而是启动循环的新迭代并创建新的间隔。一次又一次......
  3. 第一次回调1000ms延迟用完时,您现在已经排队了数千个回调。
  4. 循环继续,更多回调排队,因为self.state.tottime > 0仍然是真的。
  5. 队列太大,浏览器冻结。
  6. 解决方案

    要解决此问题,请尝试将initCount()功能更改为:

    initCount() {
      var self = this;
      this.interval = setInterval(function() {
        self.setState({tottime: self.state.tottime - 1}, function() {
          if(self.state.tottime == 0) {
            clearInterval(this.interval);
            this.interval = null;
          }
        })
      }, 1000);
    }
    

    以下工作演示:

    为了演示目的,我稍微简化了您的组件。

    &#13;
    &#13;
    class Timer extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          secs: 0,
          mins: 0,
          tottime: 10
        };
        this.initCount = this.initCount.bind(this);
      }
    
      initCount() {
        var self = this;
        this.interval = setInterval(function() {
          self.setState({tottime: self.state.tottime - 1}, function() {
            if(self.state.tottime == 0) {
              clearInterval(this.interval);
              this.interval = null;
            }
          })
        }, 1000);
      }
    
      render() {
        return (
          <div className="timer">
            <div className="timer_clock">
              <span>{this.state.mins}</span>:<span>{this.state.secs}</span>
            </div>
    
            <div className="timer_countdown"><span>{this.state.tottime}</span></div>
    
            <button onClick={this.initCount}>Go!</button>
          </div>
        );
      }
    }
    
    ReactDOM.render(<Timer />, document.getElementById("myApp"));
    &#13;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    <div id="myApp"></div>
    &#13;
    &#13;
    &#13;