1

我有一个由 2 个主要部分组成的单页应用程序:

1.-A top bar that has dynamic content(there is a shopping cart)
2.-A dynamic component that gets loaded based on the url.

有几次组件使用邮箱进行通信,问题是一旦组件本身被处理,内部创建的订阅就不是. 我知道我可以手动向每个组件添加一个 dispose 函数,然后在其中处理订阅,但是有没有办法以自动方式为所有组件执行此操作?

我确实知道如何循环所有属性并检查它们是否是订阅,但我需要一种方法以某种方式将此行为附加到所有组件,而无需手动将此 dispose 函数附加到所有组件。

我知道邮箱带有一个重置方法,我可以在我的路由库中调用,但我不想这样做,因为这样顶部栏也会失去它的订阅。

为了给你一些观点,这是主索引页面的样子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Participant Dashboard</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="">
    <!-- styles -->
    <link href="../css/bs3/bootstrap.css" rel="stylesheet">
    <link href="../css/bs3/override-bs3.css" rel="stylesheet">
    <script src="../scripts/global/requireConfig.js"></script>
    <script data-main="../consumer/scripts/require-config" src="../scripts/require.js"></script>

</head>
<body>
<top-bar params="routeParams:currentPageArguments"></top-bar>

<div data-bind="component: { name: currentPage, params: currentPageArguments }">
</div>


</body>
</html>

这是我的自定义组件加载器:

    function registerConventionLoader() {
        var koNamingConventionLoader = {
            getConfig: function (name, callback) {
                var widgetName;
                var widgetConfig = common.findComponentConfig(name);
                if (widgetConfig != null) {
                    widgetName = name.substr(widgetConfig.Prefix.length);
                    var widgetNamePascalCase = common.toPascalCase(widgetName);
                    var filePath = widgetConfig.Path;
                    var viewModelConfig = {require: filePath + widgetNamePascalCase};
                    var templateConfig = {require: "text!" + filePath + widgetNamePascalCase + '.html'};

                    callback({viewModel: viewModelConfig, template: templateConfig});
                }
                else {
                    
                    callback(null);
                }
            }
        };
        ko.components.loaders.push(koNamingConventionLoader);
    }

4

2 回答 2

2

已经实现的行为

邮箱插件dispose向你的 observables 添加了一个方法来处理它创建的任何依赖项。

dispose 函数删除 observable 对任何主题的所有订阅,以及用于自动发布对 observable 更改的所有订阅。

当调用 publishOn、subscribeTo 或 syncWith 时,此函数会附加到 observable。

资料来源:邮箱 github 文档

如果你的组件的 viewmodel 有一个dispose方法,knockout 会在移除组件时调用它。

可选地,您的视图模型类可能具有dispose功能。如果实现,Knockout 将在组件被拆除并从 DOM 中删除时调用它

来源:组件绑定

自定义处理逻辑

了解了这两个库/插件行为,我们可以得出结论,这个一般想法应该可以解决问题:

MyCustomComponent.prototype.dispose = function() {
  /* call `.dispose` on all properties that support it */
};

我们唯一需要编写的代码是注释掉的部分:

  • 循环视图模型的属性
  • 检查他们是否支持一种dispose方法
  • 如果他们这样做了就打电话

归结为:

MyCustomComponent.prototype.dispose = function() {
  var self = this;
  var propNames = Object.keys(this);

  propNames.forEach(function(key) { // Loop over vm's properties
    var val = self[key];

    if (typeof val.dispose === "function") { // Check of dispose implementation
      val.dispose(); // call dispose
    }
  });
};

或者,以不同的风格:

MyCustomComponent.prototype.dispose = function() {
  Object
    .keys(this)
    .filter(k => typeof this[k] === "function")
    .forEach(k => this[k]());
};

确保所有组件实现处置逻辑

我强烈建议使用“常规”继承或组合模式来确保您的所有组件都实现此功能。

尽管这会迫使您编辑所有组件,但它也明确地向其他读者/未来向您展示了已实现的行为

如果你真的想疯狂,你可以覆盖组件引擎的register方法以在实例化时将该方法添加到 vm,但我不推荐它:

var _register = ko.components.register;
ko.components.register = function(name, opts) {
  var ogVM = opts.viewmodel;
  opts.viewmodel = function(params) {
     ogVM.call(this, params);
     this.dispose = function() { /* ... */ }
  }
  return _register(name, opts);
};
于 2017-08-16T13:13:33.857 回答
-1

找到了!我必须从 require 中获取 viewmodel,然后附加 dispose 函数,最后在回调中传递它:感谢@user3297291 指导我正确的方向

var koNamingConventionLoader = {
            getConfig: function (name, callback) {
               
                var widgetName;
                var widgetConfig = common.findComponentConfig(name);
                
                if (widgetConfig != null) {
                    widgetName = name.substr(widgetConfig.Prefix.length);
                    var widgetNamePascalCase = common.toPascalCase(widgetName);
                    var filePath = widgetConfig.Path;
                    
                    require([filePath + widgetNamePascalCase], function (mainViewModel) {
                        mainViewModel.prototype.dispose = function () {
                            var self = this;
                            for (var property in self) {
                                if (Boolean(self[property]) && typeof self[property].dispose === "function") {
                                    
                                    self[property].dispose();
                                }
                            }
                        };
                        
                        var templateConfig = {require: "text!" + filePath + widgetNamePascalCase + '.html'};

                        callback({viewModel: mainViewModel, template: templateConfig});
                    });
                }
                else {
                    
                    console.log("widget name not resolved", name);
                    callback(null);
                }
            }
        };
        ko.components.loaders.push(koNamingConventionLoader);
    }

于 2017-08-16T18:03:58.983 回答