【问题标题】:Infinite loop with updated attribute inside a Backbone Model change event在主干模型更改事件中具有更新属性的无限循环
【发布时间】:2017-11-13 07:41:55
【问题描述】:

我正在使用 Backbone 和 ReactJS 将少量 HTML divs(我们称之为盒子)堆叠在另一个 div(我们称之为容器)中。用户可以放大这个容器,它也可以放大盒子(与 CSS 缩放属性无关)。当容器上触发缩放事件时,我计算放大框的总高度并使用堆栈的新高度更新容器。实际代码很麻烦,以下代码模拟了导致无限循环的确切场景。

在以下代码中,用户更改 ClickButton 模型的缩放值。基于zoom 值,我计算新的j 并更新它。当j 更改时,我还更新了k。现在我处于增加k 值的无限循环中。通过单击“单击”按钮在代码上进行测试。变量k 冲到 100(故意在 100 处中断,否则浏览器会死机)

我的理解: 看起来当我更新 k 或 Backbone 模型的更改处理程序中的任何内容时,它会创建另一个更改事件。一旦前一个事件结束,这个新事件就会开始执行。但是,对于函数调用 this.hasChanged("j"),这个新事件仍然返回 true,它发生在前一个事件之前并在前一个事件上处理。

目前的解决方案: 我正在使用 {silent : true} 选项作为 Backbone set 方法的输入,并成功地停止了无限循环。

我想要另一种不抑制 on change 事件的方法,因为我对 HTML 页面的更新依赖于 change 事件。我的 ReactJS 应用程序连接到模型并更新模型更改的视图。

有人知道更好的解决方案吗?

var ClickButton = Backbone.Model.extend({
  defaults: function() {
    return {
      className: "display",
      zoom: 0,
      j: 0,
      k: 0,
      children: [],
    }
  },
  initialize() {
    getElement("zoom").value = this.get("zoom");
    getElement("zoomchanged").value = 0;
    this.on("change", function(e) {
      if (this.hasChanged("j")) {
        getElement("j").value = this.get("zoom");
        if (this.get("k") < 100) {
          // Just added hundred that your browser wont get stuck
          this.set("k", parseInt(this.get("k")) + 1);
          getElement("k").value = parseInt(this.get("k"));
        }
      }
      if (this.hasChanged("zoom")) {
        getElement("zoomchanged").value = parseInt(getElement("zoomchanged").value) + 1;
        console.log(this.get("zoom"));
        getElement("zoom").value = this.get("zoom");
        this.set("j", this.get("zoom"));
      }
    });
  }
});

var clickButton = new ClickButton();

var button = getElement("button");
button.addEventListener("click", (function(e) {
  var currentValueOfZoom = clickButton.get("zoom");
  clickButton.set("zoom", currentValueOfZoom + 1);
}).bind(this));

// Helpers
function getElement(className) {
  return document.getElementsByClassName(className)[0];
}
input {
  width: 35px;
}

div#holder {
  display: flex;
}

div#holder input {
  margin-right: 23px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<div id="holder">
  <label>K: </label><input class="k" type="text"><br>
  <label>J: </label><input class="j" type="text"><br>
  <label>zoom: </label><input class="zoom" type="text"><br>
  <label>Number of times zoom changed: </label><input class="zoomchanged" type="text"><br>
  <div class="display">

  </div>
  <button class="button">
  Click
  </button>
</div>

【问题讨论】:

    标签: javascript backbone.js infinite-loop backbone-events


    【解决方案1】:

    Backbone 的silent:true option 可以防止这种行为。但是,如文档中所述,更好的解决方案是传递特定标志(例如我在下面的 sn-p 中传递并检查的 ignore: true 标志)。

    来自主干文档:

    一般来说,当调用一个发出事件的函数时(model.set、collection.add 等等...),如果你想阻止事件被触发,你可以通过 {silent: true} 作为一个选项。请注意,这很少,甚至可能永远不是一个好主意。在事件回调的选项中传递特定标志以查看并选择忽略,通常会更好。

    this.on("change", function(e, options){
        if(options.ignore !== true && this.hasChanged("zoom")){
            getElement("zoomchanged").value = parseInt(getElement("zoomchanged").value) + 1;
            console.log(this.get("zoom"));
            getElement("zoom").value = this.get("zoom");
            this.set("j", this.get("zoom"), {ignore: true});
        }
    });
    

    【讨论】:

    • 谢谢。这解决了这个问题。 +1,因为我从来不知道我可以将自定义数据作为选项传递。我一直认为选项就像 {silent : true} 这样的 Backbone 内部指令会抑制更改事件。
    【解决方案2】:

    虽然lucasjackson 提供了针对您的具体情况的有效补丁,但我还是想分享最佳实践。

    发生了什么?

    .hasChanged 仅返回通过set 所做的最新更改,并且不跟踪任何其他后续更改,除非它们是在相关的触发事件中进行的。一旦@987654327 @ 已完成,任何后续调用 set 将覆盖 changed property

    由于您在通用change 事件中更改zoomjk,因此任何后续set 都会触发一个新的change 事件,其中hasChanged 始终返回true,因为@987654337 @ 属性如下所示:

    {
        zoom: 1,
        j: 1,
        k: 100,
    }
    

    如何解决这个无限循环?

    可以做多种事情来简化和改进您的模型。首先,不要在 Backbone 模型中使用 DOM。 这是视图的工作,所以在你的情况下使用 React。

    考虑到这一点,请使用 specific attribute change event 处理程序 (change:[attribute])。

    var ClickButton = Backbone.Model.extend({
        defaults: {
            zoom: 0,
            j: 0,
            k: 0,
        },
        initialize() {
            this.listenTo(this, {
                'change:zoom': this.onZoomChange,
                'change:j': this.onJChange
            });
        },
        onZoomChange(model, value, options) {
            this.set("j", this.get("zoom"));
        },
        onJChange(model, value, options) {
            this.set("k", parseInt(this.get("k")) + 1);
        }
    });
    

    话虽如此,我不确定 Backbone 模型是否是处理按钮单击的好选择。但是话说回来,你还没有分享 React 代码……

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-16
      • 1970-01-01
      • 2014-10-08
      • 2016-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多