我经常在淘汰赛中使用if
绑定来隐藏某些东西,并且我不需要担心if
内的空引用错误。在此示例中,如果address()
为null,则删除整个块,以避免必须处理每个属性的空检查。如果我使用visible
绑定,情况就不是这样了。
<div data-bind="if: address()">
You live at:
<p data-bind="text: address().street.toUpperCase()"></p>
</div>
这是上面最简单的情况 - 是的,我通常会将此模式与<!-- ko -->
注释语法一起使用。
实际上导致我出现问题的是当我使用更复杂的computed
值并启用ko.options.deferUpdates
选项时:
<div data-bind="if: hasAddress()">
You live at:
<p data-bind="text: address().street.toUpperCase()"></p>
</div>
这个computed
可观察对象的最简单实现可能是这样的:
this.hasAddress = ko.computed(function () { return _this.address() != null; });
这一切都很有效,直到我执行以下操作:
1)在创建observable之前设置
ko.options.deferUpdates = true
。2)
address()
将从null开始,一切都很好3)将
address()
设为{ street: '123 My Street' }
。一切都运转正常。4)将
address()
重置为null。我得到一个空错误,因为address().street
是 null :-(这是一个解释问题的小提琴:https://jsfiddle.net/g5gvfb7x/2/
不幸的是,由于微任务运行的顺序,它试图在text
绑定之前重新计算if
绑定,因此您仍然会得到一个通常不会出现的空错误。发生了。
我对此有点害怕,因为我经常使用这种模式: - (
答案 0 :(得分:3)
当使用deferUpdates
时,Knockout在内部使用dirty
事件来通知所有计算的可观察对象的依赖关系发生变化并安排更新,这些更新以深度优先顺序发生。出现此问题的原因是绑定忽略dirty
事件并等待change
事件,这将以广度优先顺序发生。
必须在Knockout中进行修复才能使绑定响应dirty
事件。这已经签入了即将发布的版本(3.5.0):https://github.com/knockout/knockout/issues/2226
答案 1 :(得分:0)
好消息 - 如果您已启用ko.options.deferUpdates
并且甚至没有意识到您的应用已经损坏,则您的用户很可能无法看到错误,因为来自微观的错误任务在ko.onError
或window.onerror
上引发,UI似乎恢复。但是,如果像我一样,你会记录这些错误,那就不好了。
好消息 - 如果您可以确定在if
绑定中使用哪些可观察对象,则可以执行以下操作并关闭deferUpdates并重新打开:
// turn deferUpdates off and back on again
var deferUpdatesState = ko.options.deferUpdates;
ko.options.deferUpdates = false;
this.hasAddress = ko.computed(function () { return _this.address() != null; });
ko.options.deferUpdates = deferUpdatesState;
这可以成为辅助函数。如果计算的hasAddress
具有更复杂的依赖性,则可能存在更复杂的问题。但对于像这样简单的情况,这很好。
坏消息 - 遗憾的是,您无法执行以下操作:
ko.computed(function () { return _this.address() != null; }).extend({ deferred: false });
这只是因为deferUpdates在内部工作的方式(从查看源代码)。
仍然想知道是否有比这个辅助函数更好的解决方案,或者重写if
绑定的方法可能会在绑定中做一些聪明的事情来覆盖这种情况。
编辑:感谢@Tomalak提及with
绑定。我不认为这是我正在寻找的完整解决方案,但它肯定可以与任何现有的if
绑定(具有复杂规则)一起使用。如果尝试通过现有的应用程序,可能是最安全的解决方案。
<div>
<div data-bind="if: hasAddress">
You live at:
<!-- ko with: address() -->
<p data-bind="text: street.toUpperCase()"></p>
<!-- /ko -->
</div>
<div data-bind="if: !hasAddress()">
Sorry! I don't know where you live!
</div>
</div>