Я использую Backbone и ReactJS, чтобы сохранить несколько HTML- div
(позволяет вызывать их в виде ящиков), уложенных внутри другого div
(позволяет вызвать этот контейнер). Пользователи могут увеличивать масштаб этого контейнера, который также увеличивает масштаб (не имеет ничего общего с функцией масштабирования CSS). Когда событие масштабирования запускается в контейнере, я вычисляю общую высоту увеличенных полей и обновляю контейнер с новой высотой стека. Фактический код является громоздким, следующий код похож на точный сценарий, который приводит к бесконечному циклу.
В следующем коде пользователь меняет значение масштабирования на модель ClickButton. Основываясь на значении zoom
, я вычисляю новый j
и обновляю его. Я также обновил k
когда j
изменяется. Теперь я нахожусь в бесконечном цикле с возрастающим значением k
. Проверьте это на код, нажав кнопку "Кликнуть". Переменная k
мчится до 100 (намеренно ломается на 100, иначе браузер замерзает)
Мое понимание: похоже, что когда я обновляю k или что-то еще внутри Backbone-модели на обработчике изменений, это создает другое событие изменения. Это новое событие начинается после завершения предыдущего события. Однако это новое событие по-прежнему возвращает true для вызова функции this.hasChanged("j")
, который произошел до предыдущего события и обрабатывался.
Текущее решение: я использую опцию {silent: true}
в качестве входного сигнала для метода set
базовой линии и успешно могу остановить бесконечный цикл.
Я хотел бы еще один способ сделать это, не подавляя события изменения, поскольку мое обновление HTML-страницы зависит от изменений событий. Приложение My 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
вариант. Однако, как отмечено в документации, лучшим решением было бы передать определенный флаг (например, флаг 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});
}
});
В то время как lucasjackson предоставляет рабочий патч, специфичный для вашей ситуации, я хотел бы поделиться лучшими практиками в любом случае.
.hasChanged
возвращает только последние изменения, сделанные с помощью set
и не отслеживает никаких других последующих изменений, если они не сделаны в связанном событии запуска. После того, как set
полностью сделан, любой последующий вызов set
перепишет changed
свойство.
Поскольку вы изменяете zoom
, j
и k
внутри общего события change
, любой последующий set
запускает новое событие change
где hasChanged
всегда возвращает true, поскольку changed
свойство выглядит следующим образом:
{
zoom: 1,
j: 1,
k: 100,
}
Для упрощения и улучшения вашей модели можно сделать несколько вещей. Во-первых, не играйте с DOM внутри базовой модели. Это работа для представления, так что действуйте в вашем случае.
Имея это в виду, используйте специальные обработчики событий change:[attribute]
(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...