【问题标题】:Knockout deferUpdates conflict with 'if' binding淘汰赛 deferUpdates 与 'if' 绑定冲突
【发布时间】:2017-09-06 13:52:56
【问题描述】:

我经常在淘汰赛中使用if 绑定来隐藏某些内容,另外我不需要担心if 中的空引用错误。在此示例中,如果 address() 为 null,则整个块将被删除,因此您不必处理每个属性的 null 检查。如果我使用 visible 绑定,情况就不会如此。

<div data-bind="if: address()">
    You live at:
    <p data-bind="text: address().street.toUpperCase()"></p>            
</div>

这是上面最简单的情况 - 是的,我通常会将此模式与 &lt;!-- ko --&gt; 注释语法一起使用。

当我使用更复杂的computed 值并启用ko.options.deferUpdates 选项时,实际上导致我出现问题的原因是:

<div data-bind="if: hasAddress()">
    You live at:
    <p data-bind="text: address().street.toUpperCase()"></p>            
</div>

这个computed observable 的最简单实现可能是这样的:

this.hasAddress = ko.computed(function () { return _this.address() != null; }); 

这一切都很好,直到我执行以下操作:

1) 在创建 observables 之前设置 ko.options.deferUpdates = true

2) address() 将以 null 开始,一切正常

3) 将address() 设置为{ street: '123 My Street' }。再次一切正常。

4) 将address() 重置为空。我收到一个空错误,因为 address().street 是 空:-(

这是一个说明问题的小提琴:https://jsfiddle.net/g5gvfb7x/2/

不幸的是,由于微任务运行的顺序,它尝试在 if 绑定之前重新计算 text 绑定,因此您仍然会收到通常不会发生的空错误。


我有点害怕,因为我经常使用这种模式:-(

【问题讨论】:

  • 很好,v.3.4.2 中的同样问题。从现在开始,我将按照您的提示在开发过程中始终记录 ko.errors 和 window.errors。

标签: knockout.js


【解决方案1】:

当使用deferUpdates 时,Knockout 在内部使用dirty 事件来通知所有计算出的可观察对象它们的依赖关系发生了变化,并安排更新,这以深度优先的顺序发生。这里出现问题是因为绑定忽略了dirty 事件并等待change 事件,这将按广度优先顺序发生。

必须在 Knockout 中进行修复以使绑定响应 dirty 事件。这已经在即将发布的版本 (3.5.0) 中签入:https://github.com/knockout/knockout/issues/2226

【讨论】:

    【解决方案2】:

    好消息 - 如果您打开了 ko.options.deferUpdates 并且甚至没有意识到您已经破坏了您的应用程序,那么您的用户可能不会看到错误,因为微任务的错误会在ko.onErrorwindow.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>
    

    【讨论】:

    • 是的,我看到了所有这些 - 我的意思是在这里提到 ko.tasks.runEarly()。我会添加一个关于它的注释,但不幸的是它没有帮助。似乎只取决于任务的运行顺序。
    • 这是我多次设置runEarly jsfiddle.net/g5gvfb7x/3 的小提琴副本。是的,我完全理解风险或破坏现有应用程序,但我正在努力寻找让ifcomputed 一起使用ko.options.deferUpdates 的最佳方法,但还没有看到一个非常优雅的解决方案。
    • 是的,我只是把它扔进去,我什至还没有开始修补它。奇怪的问题,不知道是什么原因。 (TBH 我会以不同的方式写整件事,显然在这种情况下根本不会遇到这种情况jsfiddle.net/g5gvfb7x/4
    • 是的,这很有趣,但也不是很有趣。如果您只输入if: addressif 也可以正常工作。只有当我使用if: showAddressPanelif: isEditing 之类的计算时遇到更复杂的条件时才会遇到问题。这些不能轻易地简化为with 或更简单的if。至少我现在知道要寻找什么,我很高兴我正在记录 JS 错误,否则我什至从未见过这个。
    • 我已经创建了一个 Github 问题,并将尽快检查修复。 github.com/knockout/knockout/issues/2226
    猜你喜欢
    • 2013-10-31
    • 2016-10-26
    • 2014-08-01
    • 2013-02-04
    • 1970-01-01
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多