在模板上使用data-bind进行自定义绑定

时间:2018-04-02 20:52:11

标签: knockout.js

我正在创建一个自定义绑定来创建HTML元素。我的VM看起来像:

function VM() {
    this.data = ko.observableArray(['one', 'two']);
}

HTML:

<div data-bind="myBind: data"></div>
<script id="my-template" type="text/html">
    <div data-bind="text: $data"></div>
</script

我希望能够为 data 数组中的每个项创建一个新的DOM元素。问题是DOM元素需要与模板进行数据绑定。类似的东西:

ko.bindingHandlers.myBind = {
    update: function(element, valueAccessor) {
        ko.unwrap(valueAccessor()).forEach(function (x) {
            $(element).append("<div data-bind=\"template: { name: 'my-template', data:" + x + " }\"></div>")
        }
    }
}

所需的HTML输出:

<div data-bind="myBind: data">
    <div data-bind="text: $data">one</div>
    <div data-bind="text: $data">two</div>
</div>
<script id="my-template" type="text/html">
    <div data-bind="text: $data"></div>
</script

我理解标准的foreach绑定很容易实现,但是在尝试显示数千个条目时,绑定存在问题。表演很糟糕,并且花了很长时间才能做到这一点。

此外,我发现有效的解决方案是:

ko.bindingHandlers.myBind = {
    update: function(element, valueAccessor) {
        var html = "";
        ko.unwrap(valueAccessor()).forEach(function (x) {
             html += '<div>' + x + "</div>";
        }
        element.innerHTML = html;
    }
}

这个解决方案的问题在于,实际上我的模板要复杂得多,在js中编写十几行html似乎是不好的做法,难以维护,难以阅读。

我进行了搜索和搜索,我发现的所有解决方案只让我80%。这是一个相对简单的解决方案,对吗?

1 个答案:

答案 0 :(得分:2)

如果我们要帮助您编写内部使用模板的自定义绑定,我们基本上会以foreach绑定结束。淘汰的foreach绑定唯一的事情是包装template绑定:

ko.bindingHandlers['foreach'] = {
    /* ... */
    'init': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        return ko.bindingHandlers['template']['init']( /* ... */ );
    },
    'update': function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        return ko.bindingHandlers['template']['update']( /* ... */ );
    }
};

From the knockout source,为简洁起见,部分内容已经注释

如果您想优化性能,您可能需要找到另一种方法。我在使用visible绑定而不是将元素移入和移出DOM时取得了一些成功,但它确实取决于您的具体情况。

以下是您可以尝试使用此方法的示例。它的速度是否更快取决于模板和浏览器的复杂程度。我的轶事结果可能会指导您研究自己的特定用例,如下所示。 不要认为这些是理所当然的,它只是一台机器上的一个例子!

&#13;
&#13;
const filterValue = ko.observable("odd");
const pred = ko.pureComputed(() => 
  filterValue() === "odd"
    ? item => item.nr % 2 === 1
    : item => item.nr % 2 === 0
);
    
const items = Array.from(
    { length: 10000 }, 
    (_, i) => ({ title: `Item ${i}`, nr: i })
  );

const App = function() {
  const vm = this;
  vm.filterValue = filterValue;
  vm.items = items;
  
  vm.filteredItems = ko.pureComputed(() => items.filter(pred()));
  vm.isVisible = item => pred()(item);
  
  vm.visibleMode = ko.observable(false);
}



ko.applyBindings(new App());
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<label>
  <input type="radio" value="odd" data-bind="checked: filterValue"> odd
</label>
<label>
  <input type="radio" value="even" data-bind="checked: filterValue"> even
</label>
<label>
  <input type="checkbox" data-bind="checked: visibleMode"> use visible instead of foreach 
</label>

<div>
  <!-- ko if: visibleMode -->
  <ul data-bind="foreach: items">
    <li data-bind="text: title, visible: $parent.isVisible($data)"></li>
  </ul>
  <!-- /ko -->
  <!-- ko ifnot: visibleMode -->
  <ul data-bind="foreach: filteredItems">
    <li data-bind="text: title"></li>
  </ul>
  <!-- /ko -->
</div>
&#13;
&#13;
&#13;

的foreach

Performance profile of <code>foreach</code> binding

可见

Performance profile of <code>visible</code> binding