如果观察到两条变化的依赖路径,则敲除计算出的单个动作多次触发

时间:2019-03-29 14:57:14

标签: knockout.js

我有一个可观察到的A,它绑定到UI元素。 我也有一个取决于A的计算得出的B。 我有一个同时取决于A和B的С值。 我订阅了C

当更改UI元素中的值时,将对计算两次进行计算,并两次调用订阅。

我认为原因是A有两个订阅: A:[B,C]
淘汰赛将B的变化通知B。
评估B后,它会通知C B的变化
然后回到起点,并调用A的第二个订阅,即C。
在这里,我们有两个对C的调用。

有办法防止这种情况吗?

var viewModel = {
    firstName: ko.observable("Andrew"),
    lastName: ko.observable("King"),
};

viewModel.fullName = ko.computed(function() {
    return viewModel.firstName() + " " + viewModel.lastName();
});

viewModel.user = ko.computed(function() {
    return {
    fullName: viewModel.fullName(),
    lastName: viewModel.lastName()
  };
});

viewModel.user.subscribe(function() {
    // This is called once if I change first name
    // It is called twice if I change last name
});

http://jsfiddle.net/jngxwf5v/

1 个答案:

答案 0 :(得分:4)

当可观察项的依赖项之一更改时,将重新计算。由于您每次运行都会创建一个新对象,因此剔除将无法判断是否确实发生了变化。

使用延迟计算来修复它

要防止它的两个依赖项都发生更改时多次运行,可以将其推迟:

var viewModel = {
    firstName: ko.observable("Andrew"),
    lastName: ko.observable("King"),
};

viewModel.fullName = ko.computed(function() {
    return viewModel.firstName() + " " + viewModel.lastName();
});

viewModel.user = ko.computed(function() {
    return {
    fullName: viewModel.fullName(),
    lastName: viewModel.lastName()
  };
}).extend({ deferred: true });

viewModel.user.subscribe(function(user) {
  console.log(user);
});

viewModel.firstName("John");
viewModel.lastName("Doe");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

使用自定义相等比较器将其修复

解决此问题的另一种方法是添加自定义相等比较器。这使淘汰赛可以检查,当一个依赖项发生变化时,新结果是否确实不同于前一个结果。仅当两者不同时,订户才会更新。

var viewModel = {
    firstName: ko.observable("Andrew"),
    lastName: ko.observable("King"),
};

viewModel.fullName = ko.computed(function() {
    return viewModel.firstName() + " " + viewModel.lastName();
});

viewModel.user = ko.computed(function() {
  return {
    fullName: viewModel.fullName(),
    lastName: viewModel.lastName()
  };
});

viewModel.user.equalityComparer = (x, y) => x === y || x.fullName === y.fullName && x.lastName === y.lastName;

viewModel.user.subscribe(console.log);

viewModel.lastName("Doe");
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

两种方法之间的区别

在延迟的示例中,敲除排序将计算结果的重新执行推送到setTimeout。它只会运行一次,但是您不会知道“何时”。

在第二个示例中,计算函数被调用了两次(如之前一样)。唯一的区别是没有通知订户,因为这两个结果被认为是相等的。