为什么在手动重新排序和恢复 dom 项目后,挖空模板绑定停止工作?
Why does knockout template binding stop working after manually reordering - and reverting - dom items?
我正在使用删除 foreach(更具体地说,template: { foreach: items }
)绑定来显示元素列表。
然后我继续采取以下行动:
- 交换可观察数组的第一个和第二个元素。正如预期的那样,我在屏幕上看到了变化。
- 重复前面的动作恢复到初始状态。同样,这按预期工作。
- 现在,交换第一个和第二个 DOM 元素。正如预期的那样,我在屏幕上看到了变化。
- 重复前面的动作恢复到初始状态。同样,这按预期工作。
即使我们手动篡改了 DOM,我们也完全恢复到初始状态,而没有在 DOM 篡改期间调用敲除。这意味着状态已恢复到上次 knockout 意识到它时的状态,因此它看起来应该像 knockout 一样从一开始就没有任何改变。
但是,如果我再次执行第一个操作,即交换数组中的前两个元素,更改不会反映在屏幕上。
这里有一个 jsfiddle 来说明这个问题:https://jsfiddle.net/k7u5wep9/.
我知道手动篡改由 knockout 管理的 DOM 是个坏主意,它会导致未定义的行为。不幸的是,由于第三方代码,这在我的情况下是不可避免的。令我难过的是,即使将手动编辑恢复到 exact 初始状态,knockout 仍然无法按预期工作。
我的问题是:是什么导致了这种行为?
然后,如何解决它?
如果你在 foreach
周围放置一个 with: items
,它至少会继续工作,但如果 dom order != array order .. 可能会让你至少走上正轨,也许您可以重新排序 dom 函数中的 ko 数组以保持它们的 'orders' 同步?
let vm = {
items: ko.observableArray(['item1', 'item2']),
reorder_array() {
vm.items([vm.items()[1], vm.items()[0]]);
},
reorder_dom() {
let parent = document.querySelector('#items');
parent.insertBefore(parent.children[1], parent.children[0]);
vm.reorder_array();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: items">
<div id="items" data-bind="template: { foreach: $data }">
<div data-bind="text: $data"></div>
</div>
</div>
<button data-bind="click: reorder_array">Reorder array</button>
<button data-bind="click: reorder_dom">Reorder DOM</button>
<div>
Reorder the array twice, then reorder DOM twice. This should work as expected, and end up with the initial state. Then, try to reorder the array again. It should not work. Why?
</div>
- 通过操纵 DOM,您已经破坏了绑定。
- 不要直接操作DOM。 Knockout 不会检测所做的更改。
事实证明这里并没有发生什么神奇的事情。我犯的错误是只考虑元素而不是所有节点。敲除模板绑定在重新排序时保留所有节点的记录,而不仅仅是元素。
在手动编辑DOM之前,模板绑定的子节点是:
NodeList(6) [text, div, text, text, div, text]
.
使用 parent.insertBefore(parent.children[1], parent.children[0])
手动交换前两个 元素 后,变成:
NodeList(6) [text, div, div, text, text, text]
.
重复该操作产生:
NodeList(6) [text, div, div, text, text, text]
.
虽然这与仅引用元素时的初始状态相同,但当引用所有节点时则完全不同。
解决方案现在变得清晰了。执行正确手动交换的一种方法是替换
parent.insertBefore(parent.children[1], parent.children[0]);
和
let nexts = [parent.children[0].nextSibling, parent.children[1].nextSibling];
parent.insertBefore(parent.children[1], nexts[0]);
parent.insertBefore(parent.children[0], nexts[1]);
见 https://jsfiddle.net/k7u5wep9/2/.
显然,当没有文本节点时必须更加小心 before/after,但思路是一样的。
我正在使用删除 foreach(更具体地说,template: { foreach: items }
)绑定来显示元素列表。
然后我继续采取以下行动:
- 交换可观察数组的第一个和第二个元素。正如预期的那样,我在屏幕上看到了变化。
- 重复前面的动作恢复到初始状态。同样,这按预期工作。
- 现在,交换第一个和第二个 DOM 元素。正如预期的那样,我在屏幕上看到了变化。
- 重复前面的动作恢复到初始状态。同样,这按预期工作。
即使我们手动篡改了 DOM,我们也完全恢复到初始状态,而没有在 DOM 篡改期间调用敲除。这意味着状态已恢复到上次 knockout 意识到它时的状态,因此它看起来应该像 knockout 一样从一开始就没有任何改变。 但是,如果我再次执行第一个操作,即交换数组中的前两个元素,更改不会反映在屏幕上。
这里有一个 jsfiddle 来说明这个问题:https://jsfiddle.net/k7u5wep9/.
我知道手动篡改由 knockout 管理的 DOM 是个坏主意,它会导致未定义的行为。不幸的是,由于第三方代码,这在我的情况下是不可避免的。令我难过的是,即使将手动编辑恢复到 exact 初始状态,knockout 仍然无法按预期工作。
我的问题是:是什么导致了这种行为? 然后,如何解决它?
如果你在 foreach
周围放置一个 with: items
,它至少会继续工作,但如果 dom order != array order .. 可能会让你至少走上正轨,也许您可以重新排序 dom 函数中的 ko 数组以保持它们的 'orders' 同步?
let vm = {
items: ko.observableArray(['item1', 'item2']),
reorder_array() {
vm.items([vm.items()[1], vm.items()[0]]);
},
reorder_dom() {
let parent = document.querySelector('#items');
parent.insertBefore(parent.children[1], parent.children[0]);
vm.reorder_array();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: items">
<div id="items" data-bind="template: { foreach: $data }">
<div data-bind="text: $data"></div>
</div>
</div>
<button data-bind="click: reorder_array">Reorder array</button>
<button data-bind="click: reorder_dom">Reorder DOM</button>
<div>
Reorder the array twice, then reorder DOM twice. This should work as expected, and end up with the initial state. Then, try to reorder the array again. It should not work. Why?
</div>
- 通过操纵 DOM,您已经破坏了绑定。
- 不要直接操作DOM。 Knockout 不会检测所做的更改。
事实证明这里并没有发生什么神奇的事情。我犯的错误是只考虑元素而不是所有节点。敲除模板绑定在重新排序时保留所有节点的记录,而不仅仅是元素。
在手动编辑DOM之前,模板绑定的子节点是:
NodeList(6) [text, div, text, text, div, text]
.
使用 parent.insertBefore(parent.children[1], parent.children[0])
手动交换前两个 元素 后,变成:
NodeList(6) [text, div, div, text, text, text]
.
重复该操作产生:
NodeList(6) [text, div, div, text, text, text]
.
虽然这与仅引用元素时的初始状态相同,但当引用所有节点时则完全不同。
解决方案现在变得清晰了。执行正确手动交换的一种方法是替换
parent.insertBefore(parent.children[1], parent.children[0]);
和
let nexts = [parent.children[0].nextSibling, parent.children[1].nextSibling];
parent.insertBefore(parent.children[1], nexts[0]);
parent.insertBefore(parent.children[0], nexts[1]);
见 https://jsfiddle.net/k7u5wep9/2/.
显然,当没有文本节点时必须更加小心 before/after,但思路是一样的。