敲除可写计算导致不必要的 'read' 调用

knockout writeable computed causes unnecessary 'read' calls

计算出 read/write 问题:'write' 导致 'read' 执行。

我有 read/write 计算(或者也可以是纯计算)。它是可写的,因为它可以从 startDate 和 endDate 计算得出。但是,它也可以直接设置,因此它是 'readable' 并根据值设置 startDate 和 endDate。这是我的模型的简化版本:

 self.startDate = ko.observable(...);
 self.endDate = ko.observable(...);
 self.selectedTimePeriod = ko.pureComputed({
            read: function () {                
                console.log('time period read');
                var startDate = self.startDate(),
                    endDate = self.endDate(),
                    timePeriod = Enums.TimePeriodTypeEnum.None;
                timePeriod = Constants.MTD;                
                return timePeriod;
            },
            write: function (timePeriodValue) {
                console.log('time period write');
                switch (timePeriodValue) {
                    case Constants.MTD:
                        self.startDate(...);
                        self.endDate(...);
                        break;
                }
            }
    });

    self.timePeriodChange = function () {
        self.selectedTimePeriod(Constants.MTD);
    }

用户点击 UI 触发 self.timePeriodChange 函数。结果在控制台中我看到以下内容:

time period write
time period read
time period read

因此,执行了 'write' 部分,但是,当我更改 startDate 和 endDate 时 - 每次也执行 'read' 部分。我看到这是因为写入更新读取 - 但如果我不希望这样怎么办?这种情况怎么处理?

建议使用 peek,但这会导致其他问题(observable 未更新)。 这是我的小提琴手:https://jsfiddle.net/o5kacas3/(更改下拉列表,不会更改 UI 上的计算结果,实际上它的值,即使执行了写入部分)。

你所描述的实际上不是问题;这就是 write 的工作原理。

read 方法中,knockout 会创建一个 订阅 到您评估的任何可观察对象。它需要创建这些订阅,以便它可以重新评估 它自己的值 每当它依赖的另一个值发生变化时。

如果您使用 obs.peek() 而不仅仅是 obs(),您可以在不创建订阅的情况下使用一个值。这意味着您的计算机以后不会自动更新。

write 方法中,您正在设置确定 read 值的可观察值。如果您设置计算所依赖的 2 个可观察对象,您将触发 2 次读取。

为了说明:通过在下面的示例中设置计算值,并在更改之间登录,您会看到该值实际更改为 "CB" 短暂:

var reads = 0;
var a = ko.observable("A");
var b = ko.observable("B");

// Read 0 is triggered by the computed itself: 
// it wants to know its initial value
var c = ko.computed({
  read: function() {
    // This line creates subscriptions to both `a` and `b`
    var value = a() + b();
    console.log("READ " + reads++ + ": " + value);
    return value;
  },
  write: function(str) {
    console.log("WRITE");
    
    a(str[0]); // Triggers read 1, since a changed
    
    // Momentarily, the value is 'CB'
    
    b(str[1]); // Triggers read 2, since b changed
  }
});

c("CD");
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

编辑: 从评论中我现在了解到,操作不触发多次读取是必不可少的。我在评论中提出的解决方案:

重写代码以规避可写可观察对象:

var a = ko.observable("A");
var b = ko.observable("B");

var getInitialValue = function() {
  return a() + b();
};

var createNewValues = function(str) {
  a(str[0]);
  b(str[1]);
};


var c = ko.observable(getInitialValue());
c.subscribe(createNewValues);


console.log(c()); // AB
c("CD");
console.log(c()); // CD
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

避免在计算中计算中间值的最简单方法是使用 deferred updates.

Using deferred updates ensures that computed observables and bindings are updated only after their dependencies are stable. Even if an observable might go through multiple intermediate values, only the latest value is used to update its dependencies.

When applied to a computed observable, the deferred extender will also avoid excess evaluation of the computed function. Using deferred updates ensures that any sequence of changes to dependencies in the current task will trigger just one re-evaluation of the computed observable.

self.selectedTimePeriod = ko.pureComputed({
    read: function () { /*...*/ },
    write: function (timePeriodValue) { /*...*/ }
}).extend({ deferred: true });

使用此方法,您将看到以下内容:

time period write
time period read