Backbone 模型更改事件中具有更新属性的无限循环
Infinite loop with updated attribute inside a Backbone Model change event
我正在使用 Backbone 和 ReactJS 将几个 HTML div
s(我们称之为盒子)堆叠在另一个 div
(我们称之为容器)中。用户可以放大此容器,同时也可以放大框(与 CSS zoom 属性 无关)。当在容器上触发缩放事件时,我计算放大框的总高度并使用堆栈的新高度更新容器。实际代码很麻烦,下面的代码模拟了导致无限循环的确切场景。
在以下代码中,用户更改了 ClickButton 模型的缩放值。基于 zoom
值,我计算新的 j
并更新它。当 j
发生变化时,我也更新了 k
。现在我处于递增 k
值的无限循环中。通过单击 "Click" 按钮在代码上对此进行测试。变量 k
冲到 100(故意在 100 处中断,否则浏览器会冻结)
我的理解:
看起来当我更新 k 或 Backbone 模型的更改处理程序中的任何内容时,它会创建另一个更改事件。一旦前一个事件结束,这个新事件就会开始执行。但是,对于函数调用 this.hasChanged("j")
,这个新事件仍然 return 为真,它发生在上一个事件之前并在上一个事件上处理。
当前解决方案:
我正在使用 {silent : true}
选项作为 Backbone set
方法的输入,并成功地停止了无限循环。
我想要另一种方法来执行此操作而不抑制更改事件,因为我对 HTML 页面的更新依赖于更改事件。我的 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>
Backbone 的 silent:true
option 可以防止这种行为。但是,如文档中所述,更好的解决方案是传递一个特定的标志(例如我正在传递并在下面的代码段中检查的 ignore: true
标志)。
来自 backbone 文档:
Generally speaking, when calling a function that emits an event (model.set, collection.add, and so on...), if you'd like to prevent the event from being triggered, you may pass {silent: true} as an option. Note that this is rarely, perhaps even never, a good idea. Passing through a specific flag in the options for your event callback to look at, and choose to ignore, will usually work out better.
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});
}
});
虽然 提供了针对您情况的有效补丁,但我还是想分享最佳实践。
发生什么事了?
.hasChanged
only returns the latest changes made through a set
and doesn't keep track of any other subsequent changes, unless they are made in a related triggering event. Once a set
is completely done, any subsequent call to set
will overwrite the changed
property.
由于您在通用 change
事件中更改 zoom
、j
和 k
,任何后续 set
都会触发新的 change
事件,其中 hasChanged
始终 returns 为真,因为 changed
属性 看起来像这样:
{
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 代码...
我正在使用 Backbone 和 ReactJS 将几个 HTML div
s(我们称之为盒子)堆叠在另一个 div
(我们称之为容器)中。用户可以放大此容器,同时也可以放大框(与 CSS zoom 属性 无关)。当在容器上触发缩放事件时,我计算放大框的总高度并使用堆栈的新高度更新容器。实际代码很麻烦,下面的代码模拟了导致无限循环的确切场景。
在以下代码中,用户更改了 ClickButton 模型的缩放值。基于 zoom
值,我计算新的 j
并更新它。当 j
发生变化时,我也更新了 k
。现在我处于递增 k
值的无限循环中。通过单击 "Click" 按钮在代码上对此进行测试。变量 k
冲到 100(故意在 100 处中断,否则浏览器会冻结)
我的理解:
看起来当我更新 k 或 Backbone 模型的更改处理程序中的任何内容时,它会创建另一个更改事件。一旦前一个事件结束,这个新事件就会开始执行。但是,对于函数调用 this.hasChanged("j")
,这个新事件仍然 return 为真,它发生在上一个事件之前并在上一个事件上处理。
当前解决方案:
我正在使用 {silent : true}
选项作为 Backbone set
方法的输入,并成功地停止了无限循环。
我想要另一种方法来执行此操作而不抑制更改事件,因为我对 HTML 页面的更新依赖于更改事件。我的 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>
Backbone 的 silent:true
option 可以防止这种行为。但是,如文档中所述,更好的解决方案是传递一个特定的标志(例如我正在传递并在下面的代码段中检查的 ignore: true
标志)。
来自 backbone 文档:
Generally speaking, when calling a function that emits an event (model.set, collection.add, and so on...), if you'd like to prevent the event from being triggered, you may pass {silent: true} as an option. Note that this is rarely, perhaps even never, a good idea. Passing through a specific flag in the options for your event callback to look at, and choose to ignore, will usually work out better.
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});
}
});
虽然
发生什么事了?
.hasChanged
only returns the latest changes made through a set
and doesn't keep track of any other subsequent changes, unless they are made in a related triggering event. Once a set
is completely done, any subsequent call to set
will overwrite the changed
property.
由于您在通用 change
事件中更改 zoom
、j
和 k
,任何后续 set
都会触发新的 change
事件,其中 hasChanged
始终 returns 为真,因为 changed
属性 看起来像这样:
{
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 代码...