【问题标题】:React.js having state based on other stateReact.js 具有基于其他状态的状态
【发布时间】:2014-09-28 12:44:39
【问题描述】:

我在使用 React.js 时遇到了一些问题,并且在调用 setState() 时没有立即设置状态。我不确定是否有更好的方法来解决这个问题,或者它是否真的只是 React 的一个缺点。我有两个状态变量,其中一个基于另一个。 (原始问题的小提琴:http://jsfiddle.net/kb3gN/4415/您可以在日志中看到单击按钮时没有立即设置)

setAlarmTime: function(time) {
  this.setState({ alarmTime: time });
  this.checkAlarm();
},
checkAlarm: function() {
  this.setState({
    alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
  });
}, ...

在调用setAlarmTime 时,由于this.state.alarmTime 不会立即更新,因此对checkAlarm 的以下调用会根据this.state.alarmTime 的先前值设置alarmSet,因此是不正确的。

我通过将调用 checkAlarm 移动到 setAlarmTime 中的 setState 回调中解决了这个问题,但是必须跟踪什么状态实际上是“正确的”并尝试将所有内容都放入回调中似乎很荒谬:

setAlarmTime: function(time) {
  this.setState({ alarmTime: time }, this.checkAlarm);
}

有没有更好的方法来解决这个问题?我的代码中还有一些其他地方引用了我刚刚设置的状态,现在我不确定何时可以真正信任该状态!

谢谢

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    是的,setState 是异步的,所以this.state 不会立即更新。这是用于批处理的unit tests,它可能解释了一些细节。

    在上面的示例中,alarmSet 是根据alarmTimeelapsedTime 状态计算得出的数据。一般来说,计算数据不应该存储在对象的状态中,而是应该根据需要作为渲染方法的一部分进行计算。在 Interactivity and Dynamic UIs 文档的底部有一个部分 What Shouldn’t Go in State? 给出了不应进入状态的此类示例,What Components Should Have State? 部分解释了为什么这可能是一个好主意的一些原因.

    【讨论】:

    • 在大多数情况下,让状态尽可能小并在渲染函数中重新计算内容是有意义的,因为那时this.state 将完全是最新的。但是,我认为这在我的情况下不起作用,因为这个组件实际上有它自己的更新循环(使用 setInterval),它必须在每次滴答时更新和检查它的状态。因此,不能保证render 会在滴答之间运行。作为一种解决方法,我在React.createClass 上实现了一个包装器来跟踪它自己的状态。我也会在这里添加它作为答案jsfiddle.net/kb3gN/4448
    • 这里有一篇关于 RectJS 的 Flux 架构的好文章:facebook.github.io/react/docs/flux-overview.html 他们所做的一件事是从视图层中的 React 组件中分离出“复杂”状态,这可能适用于这里也。而不是将 setInterval 放在视图中,这可能是在商店中发生的事情,然后每当商店更新时,它都会触发视图重新渲染。存储可以是普通的 JS 对象,没有 setState 的异步行为。
    • 感谢您的解释
    • 想知道如果我们可以将依赖状态移动到 useEffect 是否是一个好主意,那么种子状态的更改将触发其他所有内容的更新。
    • 我的问题是:计算数据不应该存储在对象的状态中!
    【解决方案2】:

    正如 Douglas 所说,将计算状态保存在 this.state 中通常不是一个好主意,而是每次在组件的 render 函数中重新计算它,因为到那时状态已经更新。

    但是这对我不起作用,因为我的组件实际上有自己的更新循环,需要检查并可能在每次滴答时更新其状态。由于我们不能指望this.state 每次更新都会更新,我创建了一个解决方法来包装React.createClass 并添加它自己的内部状态跟踪。 ($.extend 需要 jQuery)(小提琴:http://jsfiddle.net/kb3gN/4448/

    var Utils = new function() {
      this.createClass = function(object) {
        return React.createClass(
          $.extend(
            object,
            {
              _state: object.getInitialState ? object.getInitialState() : {},
              _setState: function(newState) {
                $.extend(this._state, newState);
                this.setState(newState);
              }
            }
          )
        );
      }
    }
    

    对于在渲染函数之外需要最新状态的任何组件,只需将对 React.createClass 的调用替换为 Utils.createClass

    您还必须将所有this.setState 调用更改为this._setState,并将this.state 调用更改为this._state

    这样做的最后一个后果是您将丢失组件中自动生成的displayName 属性。这是由于替换了 jsx 转换器

    var anotherComponent = React.createClass({

    var anotherComponent = React.createClass({displayName: 'anotherComponent'.

    要解决这个问题,您只需手动将 displayName 属性添加到您的对象。

    希望对你有帮助

    【讨论】:

    • 一种更简单的方法可能是将您的状态完全存储在 react 之外,并将其作为 props 传递给 react - 然后您的状态始终是最新的,并且 react 可能尚未协调,这并不重要.
    【解决方案3】:

    不确定问问题时是否是这种情况,尽管现在this.setState(stateChangeObject, callback) 的第二个参数采用了一个可选的回调函数,所以可以这样做:

    setAlarmTime: function(time) {
      this.setState({ alarmTime: time }, this.checkAlarm);
    },
    checkAlarm: function() {
      this.setState({
        alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
      });
    }, ...
    

    【讨论】:

    • 您永远不应该在this.setState(...) 中使用this.state。因为this.setState(...) 是异步的(大部分时间)你无法预测this.state 在执行时的内容是什么。 this.setState(prevState =&gt; ({ alarmSet: prevState.alarmTime &gt; 0 &amp;&amp; prevState.elapsedTime &lt; prevState.alarmTime })); 是正确的做法。
    猜你喜欢
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    • 2016-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-23
    相关资源
    最近更新 更多