Knockout JS 不清除组件

Knockout JS not clearing components

所以这是一个我以前从未真正遇到过的奇怪的 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" 以发布子方式与页面通信。

当我将它们放入页面时,我按照以下方式进行操作:

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

其中'data'如上JSON所示

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

如果我必须做一个 "reload" 的页面,我只是

pageComponentsToDisplay = ko.observableArray([]);

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

pageComponentsToDisplay(data);

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

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

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

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

这是我第一次遇到这种敲除行为,我已经使用这种模式多年,没有出现任何问题。

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

https://github.com/shawty/dotnetnotts15

如果有人对这里可能发生的事情有任何想法,我很乐意听取他们的意见。

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

问候 美女

更新 1

所以在进一步挖掘之后(以及一点 'new thinking' 感谢 cl3m 的回答)我更进一步了。

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

事实证明,我的 'Components' 正在被处理和拆除,但正在创建以响应 postbox 的订阅处理程序却没有。

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

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

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

任务继续。

更新 2

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

我不确定这是否有帮助,但根据我的评论,这是我在 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

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

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

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

一直以来,因为我只以这种方式使用单次订阅,所以我从来没有支付 任何 对值的关注 return 邮箱订阅电话。

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

然而事实证明,当您以这种方式使用邮箱时,没有任何可观察对象可供您定位,因此也没有什么可处理的。 (你没有保存 return,所以你没有什么可以使用的)

Knockout 和 Postbox 文档没有提到的是 postbox.subscribe 中的 return 值是一个通用的 Knockout 订阅函数,通过将它的 return 分配给一个属性 在您的模型中,然后您可以调用其上可用的功能,其中一个功能提供 "dispose" 实例的能力,这不是仅从其集合中删除组件的物理表现,但也确保连接到它的任何订阅或事件处理程序也被正确拆除。

此外,您可以在注册 VM 时将处置处理程序传递给您的 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 };
});

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

如您在我的示例中所见,我已如前所述从订阅处理程序中保存了 return,然后在这个我们知道将被调用的挂钩点中使用它来确保我也在每个订阅上调用 dispose。

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