【问题标题】:Knockout JS not clearing componentsKnockout JS 不清除组件
【发布时间】:2016-07-07 18:29:27
【问题描述】:

所以这是一个我以前从未真正遇到过的奇怪的 KnockoutJS 问题。

我正在开发一个大量使用 Knockout 组件的应用程序。

在应用程序的一部分中,我有一个编辑器页面,该页面由 JSON 驱动的后端动态构建,并根据后端数据告诉它的内容,使用许多小部件填充前端页面。

示例 后端可能会发送

[{"widget": "textBox"},{"widget": "textBox"},{"widget": "comboBox"},{"widget": "checkBox"}]

这会导致前端构建一个包含

的页面
<html>
  ....
  <textbox></textbox>
  <textbox></textbox>
  <combobox></combobox>
  <checkbox></checkbox>
  ....
</html>

每个自定义标签都是一个单独的 KnockoutJS 组件,编译为 AMD 模块并使用 RequireJS 加载,每个组件都基于相同的样板:

/// <amd-dependency path="text!application/components/pagecontrols/template.html" />
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) {
    var Template = require("text!application/components/pagecontrols/template.html");
    var ViewModel = (function () {
        function ViewModel(params) {
            var _this = this;
            this.someDataBoundVar = ko.observable("");
        }
        ViewModel.prototype.somePublicFunction = function () {
            postbox.publish("SomeMessage", { data: "some data" });
        };
        return ViewModel;
    })();
    return { viewModel: ViewModel, template: Template };
});

组件相互通信并使用“Knockout Postbox”以 pub sub 方式与页面通信。

当我将它们放入页面时,我会在以下庄园中这样做:

<div data-bind="foreach: pageComponentsToDisplay">
    <!-- ko if: widget == "textBox" -->
    <textBox params="details: $data"></textBox>
    <!-- /ko -->

    <!-- ko if: widget == "comboBox" -->
    <comboBox params="details: $data"></comboBox>
    <!-- /ko -->

    <!-- ko if: widget == "checkBox" -->
    <checkBox params="details: $data"></checkBox>
    <!-- /ko -->
</div>

其中 pageComponentsToDisplay 是一个简单的剔除可观察数组,我只是将从后端接收到的对象推送到:

pageComponentsToDisplay = ko.observableArray([]);
pageComponentsToDisplay(data);

“数据”在上面的 JSON 中显示

现在所有这些都很好用,但现在是 ODD 部分。

如果我必须“重新加载”页面,我只需

pageComponentsToDisplay = ko.observableArray([]);

清除数组,因此,我的所有组件也按预期从页面中消失,但是当我加载新数据时,再次使用:

pageComponentsToDisplay(data);

我的新组件按预期显示在屏幕上,但旧组件似乎仍然存在并在内存中处于活动状态,即使它们不可见。

我知道控件仍然存在的原因是,当我发出一条 PubSub 消息以向控件询问某些状态信息时,它们都回复了。

在我看来,当我清除数组时,当 KO 清除视图模型时,它实际上似乎并没有破坏旧副本。

此外,如果我再次刷新,我会得到 3 组组件响应,再次刷新是 4 组,并且按预期不断增加。

这是我第一次在淘汰赛中遇到这种行为,而且我多年来一直使用这种模式没有问题。

如果您想对整个项目的设置有一个很好的了解,我的 github 页面上有一个示例骨架布局:

https://github.com/shawty/dotnetnotts15

如果有人对这里可能发生的事情有任何想法,我很想听听。

作为最后一点,我实际上是使用 Typescript 开发所有这些,但由于这是一个运行时问题,我从 JS 的角度记录它。

问候 肖蒂

更新 1

因此,在进一步挖掘之后(感谢 cl3m 的回答,有了一点“新思维”),我向前迈进了一点。

在我最初的帖子中,我确实提到我正在使用 Ryan Niemeyer 出色的 PubSub 扩展来实现 Knockout 'ko postbox'。

事实证明,我的“组件”正在被处置和拆除,但为响应邮箱而创建的订阅处理程序却没有。

结果是,VM(或更具体地说,订阅在 VM 中使用的值)与邮箱订阅处理程序一起保存在内存中。

这意味着当我的主人广播一条消息询问组件值时,持有的引用会响应,然后是可见的活动组件。

我现在需要做的是找出一种处理这些订阅的方法,因为我直接使用邮箱,而不是将它们分配给我的模型中的可观察对象,这意味着我实际上没有 var 或对象引用来定位他们。

任务继续。

更新 2

查看我对以下问题的自我回答。

【问题讨论】:

  • 您是否使用ko.utils.domNodeDisposal.addDisposeCallback() 方法在从DOM 中删除相应元素后处理您的剔除组件?
  • 我不知道,我只是清除了数组,我假设它会自动拆除我的组件实例。我必须承认,但是当我在周五研究这个时,我遇到了提到它的文档,但还没有考虑使用它。简而言之,除非 KO 自动为我做,否则我不会。
  • 我正在使用这种方法进行自定义绑定,但是我不会在组件上尝试它......
  • 你用的和我一样的方法,还是你用的addDisposeCallBack?如果是后者,您介意发布样本吗?
  • 我使用的是addDisposeCallBack。请参阅我的答案中的示例。我不确定它会不会有多大帮助:S

标签: javascript knockout.js requirejs web-component


【解决方案1】:

我不确定这会有所帮助,但根据我的评论,这是我在custom bindings 中使用ko.utils.domNodeDisposal.addDisposeCallback() 的方式。也许有办法在淘汰赛中使用它components

ko.bindingHandlers.tooltip = {
    init: function(element, valueAccessor) {
      $(element).tooltip(options);
      ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
        $(element).tooltip('destroy');
      });
   }
}

更多阅读Ryan Niemeyer's website

【讨论】:

  • 感谢您现在进行测试和跟进,并通知您。
  • 刚刚对此有所了解,帮助不大,但给了我一些想法。我假设您使用它来杀死 Bootstrap 组件?
  • 是的,在其他库中,我将其应用于我的元素。谷歌搜索你的问题,我偶然发现了这个文档:knockoutjs.com/documentation/… 也许dispose () 方法可以提供帮助
  • 是的,我现在正在玩这种可能性......我可以在需要时触发 Dispose,但我仍然不知道如何“拆除组件”和摧毁它。 (如果这是有道理的)
  • 您可能还想看看无障碍事件处理:knockoutjs.com/documentation/unobtrusive-event-handling.html
【解决方案2】:

问题似乎是由于 Knockout 在实际组件处于活动状态时挂在邮箱设置的订阅上。

就我而言,我纯粹将邮箱用作消息传递平台,所以我所做的只是

ko.postbox.subscribe("foo", function(payload) { ... });

一直以来,由于我只以这种方式使用单次订阅,因此我从未ANY关注邮箱订阅调用返回的值。

我这样做是因为在我创建的许多组件中都有一个它们都使用的通用 API,但它们都以不同的方式响应,所以我只需要一个简单的 this is what to当您调用的处理程序是特定于组件而不是特定于应用程序时执行。

然而事实证明,当您以这种方式使用邮箱时,您没有可观察到的目标,因此没有什么可处置的。 (你没有保存退货,所以你没有什么可做的)

Knockout 和 Postbox 文档没有提到的是,来自 postbox.subscribe 的返回值是一个通用的 Knockout 订阅函数,通过将返回值分配给模型中的属性,您就可以调用其上可用的功能,其中一个功能提供了“处置”实例的能力,这不仅从它的集合中删除组件的物理表现,而且还确保任何订阅或事件处理程序与之相连的也正确拆除。

再加上,您可以在注册时将处置处理程序传递给您的虚拟机,最终的解决方案是确保您执行以下操作

/// <amd-dependency path="text!application/components/pagecontrols/template.html" />
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) {
    var Template = require("text!application/components/pagecontrols/template.html");
    var ViewModel = (function () {
        function ViewModel(params) {
            var _this = this;
            this.someDataBoundVar = ko.observable("");
            this.mySubscriptionHandler = ko.postbox.subscribe("foo", function(){
              // do something here to handle subscription message
            });
        }
        ViewModel.prototype.somePublicFunction = function () {
            postbox.publish("SomeMessage", { data: "some data" });
        };
        return ViewModel;
        ViewModel.prototype.dispose = function () {
          this.mySubscriptionHandler.dispose();
        };
        return ViewModel;
    })();
    return { viewModel: ViewModel, template: Template, dispose: this.dispose };
});

你会注意到生成的类也有一个“dispose”函数,这是 KnockoutJS 在组件类上提供的东西,如果你的类被主 KO 库作为组件管理,KO 将查找并执行如果找到,当您的组件类超出范围时该函数。

正如您在我的示例中看到的那样,我保存了之前提到的订阅处理程序的返回,然后在这个我们知道将被调用的挂钩点中,使用它来确保我也在每个订阅上调用 dispose。

当然这只会显示一个订阅,如果您有多个订阅,那么您需要多次保存,最后需要多次调用。实现这一点的一种简单方法,特别是如果您像我一样使用 Typescript,是使用 Typescripts 泛型功能并将所有订阅保存到类型化数组中,这意味着最后您需要做的就是遍历该数组并调用 dispose on里面的每一个条目。

【讨论】:

  • 您可能已经为我节省了几天非常沮丧的尝试来找出原因!非常感谢这个答案!
  • 非常欢迎杰米。这件事让我至少转了一个星期。作为额外的奖励,如果您使用 Aurelia,则使用事件库的过程完全相同。
  • 太棒了。我没有使用 Aurelia,但可能会在未来的项目中使用它,所以感谢您的提示。一切顺利!杰米
  • 如果你想要一个使用 Knockout 的摇滚应用程序骨架,以及解决这个问题的应用程序,你可以在我的 github 页面上找到它@github/shawty
  • PS:我现在完全搬到了 Aurelia,它很震撼,而且太棒了,我可能应该写一些关于它的博客...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 2016-06-14
  • 2014-04-10
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
相关资源
最近更新 更多