自动处理ko邮箱下标

时间:2017-08-15 20:44:54

标签: knockout.js knockout-postbox

我有一个单页应用程序,包含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);
    }

2 个答案:

答案 0 :(得分:2)

已经实施的行为

邮箱插件向您的observable添加dispose方法以处置它创建的任何依赖项。

  

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

     

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

     

来源:postbox github docs

如果组件的viewmodel具有dispose方法,则knockout会在删除组件时调用它。

  

或者,您的viewmodel类可能具有dispose功能。如果实现了,Knockout将在拆除组件并从DOM中删除

时调用它      

来源:component-binding

自定义处理逻辑

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

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

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

  • 遍历viewmodel的属性
  • 检查它们是否支持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);
};

答案 1 :(得分:-1)

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

&#13;
&#13;
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);
    }
&#13;
&#13;
&#13;