Knockout JS没有清除组件

时间:2016-03-21 09:55:40

标签: javascript knockout.js requirejs web-component

所以这是一个奇怪的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 };
});

组件使用“淘汰邮箱”以pub子方式相互通信并与页面进行通信。

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

<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是一个简单的knockout可观察数组,我只是将从后端接收的对象推送到:

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中使用的值)与邮箱订阅处理程序一起保存在内存中。

这意味着当我的主人广播询问组件值的消息时,保持的参考响应,然后是明显活动的组件。

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

任务继续。

更新2

请参阅我对以下问题的自我回答。

2 个答案:

答案 0 :(得分:1)

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

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

更多阅读Ryan Niemeyer's website

答案 1 :(得分:1)

看起来问题是由于当实际组件处于活动状态时,Knockout会挂在邮箱设置的订阅上。

在我的情况下,我将邮箱纯粹用作邮件平台,所以我所做的就是

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

一直以来,因为我只是以这种方式使用单拍订阅,所以我从不支付 ANY 注意邮箱订阅调用返回的值。

我这样做是因为在我创建的许多组件中都有一个他们都使用的通用API,但是他们都以不同的方式响应,所以我所需要的只是一个简单的这就是当您的被叫处理程序是特定于组件但不是特定于应用程序时。

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

Knockout和Postbox文档没有提到的是,postbox.subscribe的返回值是一般的Knockout订阅功能,通过将返回值分配给模型中的属性,您就可以调用它上面提供的功能,其中一个功能提供了&#34; dispose&#34; 实例的功能,它不仅仅从组件的集合中删除了组件的物理表现形式,但也确保连接到它的任何订阅或事件处理程序也被正确拆除。

与此相关,您可以在注册时将一个dispose处理程序传递给您的VM,最终的解决方案是确保您执行以下操作

/// <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 };
});

你会注意到由此产生的课程有一个&#34; dispose&#34;也是函数,这是KnockoutJS在组件类上提供的东西,如果您的类由主KO库作为组件进行管理,KO将在组件类超出范围时查找并执行(如果找到)该函数。

正如您在我的示例中所看到的,Iv已经保存了前面提到的订阅处理程序的返回,然后在我们知道将被调用的这个挂钩点中,用于确保我也在每个上面调用dispose订阅。

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