自定义绑定中的正确依赖跟踪

Proper dependecy tracking in a custom binding

我想要实现的是 tr 的方式在视觉上过滤 foreach 绑定 生成的 table 行被过滤掉的行的元素将被隐藏而不是从 DOM.
中删除 当用户更改过滤条件时,此方法会显着提高渲染性能。这就是为什么我不希望 foreach 绑定到根据过滤条件更新的计算可观察数组。
我希望这个解决方案成为一个随时可用的构建块,我可以在项目的其他地方使用它。

就我对 Knockout 的熟悉程度而言,最好的方法是实现自定义绑定。

我打算使用此绑定的方式是这样的:

<tbody data-bind="foreach: unfilteredItems, visibilityFilter: itemsFilter">
    <tr>
    ...
    </tr>
</tbody>

其中 itemsFilter 是一个返回 boolean 的函数,具体取决于当前行是否可见,如下所示:

    self.itemsFilter = function (item) {
        var filterFromDate = filterFromDate(), // Observable
            filterDriver = self.filterDriver(); // Observable too

        return item && item.Date >= filterFromDate && (!filterDriver || filterDriver === item.DriverKey);
    };

这是我目前的绑定实现:

/*
 * Works in conjunction with the 'foreach' binding and allows to perform fast filtering of generated DOM nodes by
 * hiding\showing them rather than inserting\removing DOM nodes.
*/
ko.bindingHandlers.visibilityFilter = {
    // Ugly thing starts here
    init: function (elem, valueAccessor) {
        var predicate = ko.utils.unwrapObservable(valueAccessor());

        predicate();
    },
    // Ugly thing ends
    update: function (elem, valueAccessor) {
        var predicate = ko.utils.unwrapObservable(valueAccessor()),
            child = ko.virtualElements.firstChild(elem),
            visibleUpdater = ko.bindingHandlers.visible.update,
            isVisible,
            childData,
            trueVaueAccessor = function () { return true; },
            falseVaueAccessor = function () { return false; };

        while (child) {
            if (child.nodeType === Node.ELEMENT_NODE) {
                childData = ko.dataFor(child);

                if (childData) {
                    isVisible = predicate(childData, child);
                    visibleUpdater(child, isVisible ? trueVaueAccessor : falseVaueAccessor);
                }
            }

            child = ko.virtualElements.nextSibling(child);
        }
    }
};
ko.virtualElements.allowedBindings.visibilityFilter = true;

你看到丑陋的 init 部分没有传递对象给谓词调用了吗?

如果没有这个,如果 Knockout 第一次调用 update 方法时 foreach 绑定没有生成任何行,则不会调用 itemsFilter 过滤函数。
因此,不会读取任何可观察对象,并且 KO 依赖跟踪机制决定此绑定不依赖于我的视图模型中的任何可观察对象。
当过滤器可观察值(filterFromDatefilterDriver)的值发生变化时,update 将永远不会被再次调用并且整个过滤不起作用。

我怎样才能改进这个实现(或解决问题的整个方法),以免对过滤器函数进行丑陋的调用,这至少会使函数等待一个undefined 值作为参数?

您可以在 tr 上使用 visible 绑定,并将其绑定到使用 $data 作为参数的函数调用的结果。下面是一个小演示。 select 中的任何值都会从 table.

中过滤掉

var vm = {
  rows: ko.observableArray(['One', 'Two', 'Three']),
  selected: ko.observable('One'),
  isVisible: function(row) {
    return row !== vm.selected();
  }
};

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options:rows, value:selected"></select>
<table border="1" data-bind="foreach:rows">
  <tr data-bind="visible:$parent.isVisible($data)">
    <td data-bind="text:$data"></td>
  </tr>
</table>