在自定义绑定中扩展可观察对象

Extending an observable in a custom binding

我有一个自定义绑定处理程序,我要绑定到我的视图模型中的一个复杂对象。

绑定处理程序正常工作,并且当任何 observable 的属性更新时调用 update 函数。但是, 每个 更新 属性 都会调用 update 函数,这会导致奇怪的行为,因为我依赖于整个对象可用并且是最新的。

我理解为什么会发生这种情况,因为每个 属性 都会导致调用更新,而且我想我知道如何防止这种情况发生 - 通过使用 Knockout 的 deferred updates 功能。

但是,我无法找到如何只为我的自定义绑定中的可观察对象启用延迟更新。我不想在应用程序范围内启用它,因为我正在将绑定编写为库函数。

我尝试过很多不同的方法,包括:

所有这些都没有用。

我还没有找到任何其他与此类函数非常接近的自定义绑定处理程序,并且一直在尝试将其与其他函数拼凑在一起。

我的绑定代码本身比较简单,我只是获取绑定对象并简单地拆分出参数并将它们传递给代码镜像实例。

ko.bindingHandlers.editor = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var observableValue = ko.utils.unwrap(valueAccessor());
        initEditor(element, observableValue, allBindingsAccessor);
    },
    update: function(element, valueAccessor, allBindingsAccessor) {
        var observableValue = ko.unwrap(valueAccessor());

        createEditor(codeEditorDiv, observableValue);
        resize();
        updateEditor(element, observableValue, allBindingsAccessor);
    }
};

我的 HTML 代码是:

 <div id="editor" data-bind="editor: EditorVM"></div>

我正在为 ViewModel 使用 Dotnetify,因此它是一个合理的复杂 C# class,但足以说明绑定正在工作和更新,但我需要它只调用 'update' 更新所有属性后。

很遗憾,您没有展示 initEditorcreateEditorupdateEditorobservableValue 的作用,因为这可能是您应该扩展可观察对象的地方。

绑定的 initupdate 方法创建计算依赖项,这意味着 any observable 在从 [= 开始的调用堆栈中展开18=] 将导致调用 update 方法。

在一个抽象的例子中:

const someVM = ko.observable({
  a: ko.observable(1),
  b: ko.observable(2),
  c: ko.observable(3)
});

// Some function that unwraps properties
const logABC = function(vm) {
  console.log(
    vm.a(),
    vm.b(),
    vm.c()
  );
}

// Regular binding update:
ko.computed(function update() {
  console.log(
    "Regular binding update:",
  )
  logABC(someVM())
});

// Change VM
someVM(someVM());

// Change a, b, and c
someVM().a("A");
someVM().b("B");
someVM().c("C");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

注意update被称为:

  1. 初始化计算时
  2. 更改包含视图模型的可观察对象时
  3. 当更改 任何 视图模型的可观察属性时

有几种解决问题的方法,其中最简单的是在绑定的 init 方法中创建自己的 computed 并将其扩展为 deferred

const someVM = ko.observable({
  a: ko.observable(1),
  b: ko.observable(2),
  c: ko.observable(3)
});

const getABC = function(vm) {
  return [vm.a(), vm.b(), vm.c()].join(", ");
}

ko.bindingHandlers.renderABC = {
  init: function(el, va) {
    el.innerText += "Init.\n";
    
    // This ensures any inner unwrapping gets deferred
    var updateSub = ko.computed(function update() {
      el.innerText += getABC(ko.unwrap(va())) + "\n";
    }).extend({ deferred: true });
    
    ko.utils.domNodeDisposal.addDisposeCallback(el, function() {
      updateSub.dispose();
    });
  }
}

ko.applyBindings({ someVM: someVM });

// Change VM
someVM(someVM());

// Change a, b, and c
someVM().a("A");
someVM().b("B");
someVM().c("C");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<pre data-bind="renderABC: someVM"></pre>