简体   繁体   English

React.js了解setState

[英]React.js Understanding setState

I've been having a go at learning React.js by writing a small calculator application. 我一直在通过编写一个小型计算器应用程序来学习React.js。 I thought things were going quite well until I learned that setState is asynchronous and my mutations therefore do not get immediately applied. 我以为事情进展得很顺利,直到我了解到setState是异步的,因此我的突变没有立即得到应用。

So my question is, what is the best way to keep a running total based upon the values being added to an input. 所以我的问题是,根据添加到输入中的值,保持运行总计的最佳方法是什么? Take the following example: 请看以下示例:

var Calculator = React.createClass({
  total : 0,

  getInitialState : function(){
    return { 
      value : '0'
    };
  },

  onValueClicked : function (value) {
    var actual, total, current = this.state.value;

    if(value === '+') {
      actual = this.total = parseInt(this.total, 10) + parseInt(current, 10);
    } else {
      if(parseInt(current, 10) === 0) {
        actual = value;
      } else {
        actual = current.toString() + value;
      }
    }

    this.setState({ value : actual });
  },

  render : function () {
    return (
      <div className="calc-main">
        <CalcDisplay value={this.state.value} />
        <CalcButtonGroup range="0-10" onClick={this.onValueClicked} />
        <CalcOpButton type="+" onClick={this.onValueClicked} />
      </div>
    )
  }
});

var CalcDisplay = React.createClass({
  render : function () {
    return (
      <input type="text" name="display" value={this.props.value} />
    );
  }
});

var CalcButtonGroup = React.createClass({
  render : function () {
    var i, buttons = [], range = this.props.range.split('-');

    for(i = range[0]; i < range[1]; i++) {
      var handler = this.props.onClick.bind(null, i);

      buttons.push(<CalcNumberButton key={i} onClick={ handler } />);
    }

    return (
      <div className="calc-btn-group">{ buttons }</div>
    );
  }
});

var CalcNumberButton = React.createClass({
  render : function () {
    return (
      <button onClick={this.props.onClick}>{this.props.key}</button>
    );
  }
});

var CalcOpButton = React.createClass({
  render : function () {
    var handler, op = this.props.type;

    handler = this.props.onClick.bind(null, op);

    return (
      <button onClick={handler}>{op}</button>
    );
  }
});

React.renderComponent(<Calculator />, document.getElementById('container'));

In the example above I gave up completely on storing the total within the state and kept it outside. 在上面的示例中,我完全放弃了将总计存储在状态内并将其保存在状态外的方法。 I've read that you can have a callback run when setState has finished but in the case of a calculator I need it to be snappy and update quickly. 我读过,当setState完成时,您可以运行一个回调,但是对于计算器,我需要它要快速且快速更新。 If the state isn't getting updated with each button press and I quickly hit the buttons - things are going to fall out of sync. 如果每次按下按钮时状态都没有更新,而我很快就按下了按钮-事情将不同步。 Is the callback all I am missing or am I thinking about this in completely the wrong way? 是我所缺少的回调函数,还是我完全以错误的方式考虑了此问题?

Any help appreciated! 任何帮助表示赞赏!

It's asynchronous, but much faster than the fastest possible human click. 它是异步的,但比最快的人工点击要快得多。


Aside from that, you should declare instance variables in componentDidMount, eg 除此之外,您应该在componentDidMount中声明实例变量,例如

componentDidMount: function(){
  this.total = 0;
}

... but in this case you probably want to store it in state. ...但是在这种情况下,您可能希望将其存储在状态中。


.split returns an array of strings, you want to be using numbers: .split返回一个字符串数组,您想使用数字:

range = this.props.range.split('-').map(Number)

Or avoid the strings altogether (prefered) with one of these: 或完全避免使用以下任一字符串(首选):

<CalcButtonGroup range={[0, 10]} onClick={this.onValueClicked} />
<CalcButtonGroup range={{from: 0, till: 10}} onClick={this.onValueClicked} />

You have define the total variable for your business logic state. 您已经为业务逻辑状态定义了total变量。 Why not store more information like that? 为什么不存储更多这样的信息?

var Calculator = React.createClass({
  previous: 0, // <-- previous result
  current: 0, // <-- current display
  op: '', // <-- pending operator

  getInitialState : function(){
    return { 
      value : '0'
    };
  },

  onValueClicked : function (value) {
    var actual;

    if(value === '+') {
      this.previous = this.current;
      this.op = '+';
      actual = 0; // start a new number
    } else if (value === '=') {
      if (this.op === '+') {
        actual = this.previous + this.current;
      } else {
        actual = this.current; // no-op
      }
    } else {
      actual = current * 10 + value;
    }

    this.current = actual; // <-- business logic state update is synchronous
    this.setState({ value : String(actual) }); // <-- this.state is only for UI state, asynchronous just fine
  },

  render : function () {
    return (
      <div className="calc-main">
        <CalcDisplay value={this.state.value} />
        <CalcButtonGroup range="0-10" onClick={this.onValueClicked} />
        <CalcOpButton type="+" onClick={this.onValueClicked} />
        <CalcOpButton type="=" onClick={this.onValueClicked} />
      </div>
    )
  }
});

The basic idea to resolve this.state is use other variables to store your business logic state, and reserve this.state only for UI state. 解决this.state的基本思想是使用其他变量存储您的业务逻辑状态,并将this.state仅保留给UI状态。

PS. PS。 A real calculator has more complex business logic than this. 真正的计算器要比这复杂得多。 You should define every state and state machine clearly in spec. 您应该在规范中明确定义每个状态和状态机。

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

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