为啥以这种方式更改此格式化函数会导致 Knockout 绑定开始工作?

Posted

技术标签:

【中文标题】为啥以这种方式更改此格式化函数会导致 Knockout 绑定开始工作?【英文标题】:Why did changing this formatting function in this way, cause the Knockout binding to start working?为什么以这种方式更改此格式化函数会导致 Knockout 绑定开始工作? 【发布时间】:2021-03-08 04:00:36 【问题描述】:

我在页面上有一个元素,如下所示:

<span data-bind="text: FormattedCountOfPeople"></span>

而计算它的函数我第一次是这样写的:

self.FormattedCountOfPeople = ko.computed(function () 
    if(!self.PeopleCountFormatString)                 //a string like "0 people in the conference", but set by a service call so starts out undefined
      return "not set yet";

    let sizes = self.Params().PermittedCounts();      //an array of discrete permitted sizes e.g. [2, 10, 30]
    let size = self.Params().ChosenCount();           //value set by a jquery slider, could be any double, even 5.7435 - to achieve smooth dragging of the slider it has a small step but snaps to a permitted count
    let n = nearest(size, sizes);                     //a "round to nearest" helper function that given args of e.g. (5.7345, [2, 10, 30]) will pick 2 because 5.7345 is nearer to 2 than 10
    let s = self.PeopleCountFormatString.csFormat(n); //csformat is a helper function that performs C# style string Formatting e.g. "0 people".csFormat(2) -> "2 people"
    return s;
);

我转了几个小时想知道为什么页面上的文本只是卡在“尚未设置”,不管滑块设置是什么,但是作为测试添加的另一个元素 &lt;span data-bind="text: Params().ChosenCount"&gt;&lt;/span&gt; 正在完美更新在其他地方,使用类似的逻辑,一个不同的滑块可以很好地设置小时和分钟的持续时间:

//if the user picks 61.2345, it rounds to 60, then returns "1 hour". Picking 74.11 rounds to 75 then returns "1 hour, 15 min"
self.FormattedDurationText = ko.computed(function () 
    var duration = self.Params().ChosenDuration();  
    duration = Math.round(duration / 15) * 15;

    if (Math.floor(duration / 60) > 0)
        var hours = self.DurationTextHours.csFormat(Math.floor(duration / 60));
    if (duration % 60 > 0)
        var minutes = self.DurationTextMinutes.csFormat(duration % 60);
    if (minutes && hours)
        return self.DurationTextLayout.csFormat(hours, minutes)
    if (hours && !minutes)
        return hours;
    if (!hours && minutes)
        return minutes;
    return "";
);

在不同的地方添加一些控制台日志,很明显,在第一次调用 FormattedCountOfPeople 返回“尚未设置”之后,在拖动人数滑块时再也不会调用 FormattedCountOfPeople。直接绑定到原始Params().ChosenCount 的另一个跨度将不断更新,因此值正在改变。同样,绑定到Params().ChosenDuration 的滑块正在更新值,并且每次值更改时都会调用FormattedDuration(),并提供一个新的格式化字符串以进入范围

最后,使事情正常进行的代码更改似乎非常无关紧要。我删除了最初的 if 并将其换成内联条件:

self.FormattedCountOfPeople = ko.computed(function () 
    let sizes = self.Params().PermittedCounts();
    let size = self.Params().ChosenCount();
    let n = nearest(size, sizes); 
    let s = (self.PeopleCountFormatString ? self.PeopleCountFormatString: "not set yet").csFormat(n); 
    return s;

为什么突然做出这个改变意味着每次值改变时,淘汰赛开始调用FormattedCountOfPeople?我能看到的唯一真正不同的是FormattedDurationText 的结构使得它总是调用例如Params().ChosenDuration() 在确定值不好并返回 "" 之前,FormattedCountOfPeople 的第一个版本有一个行为,偶尔决定不调用任何东西,而第二个版本总是调用。调用Params().Xxx()会不会有副作用,比如“resetting a flag”,如果flag永远不会被reset,那么knout即使值改变也不会再次调用该函数?

【问题讨论】:

【参考方案1】:

当您在计算对象中引用一个可观察对象时,KO 将在内部订阅该可观察对象,因此每次可观察对象更改时都会重新评估计算对象。

当你在计算开始时这样做:

if(!self.PeopleCountFormatString)
  return "not set yet";

并且self.PeopleCountFormatString 不是 一个可观察的(它似乎不是),由于返回语句,订阅ChosenCount,您的计算不会进一步评估永远不会发生,并且因为PeopleCountFormatString 本身也不是可观察的,所以当PeopleCountFormatString 确实有值时,计算不会在以后重新评估。因此,它的值将永远保持“尚未设置”。

更新后的计算有效,因为您立即引用其他可观察量,因此 KO 将在内部订阅这些并在可观察量发生变化时重新评估计算量。

【讨论】:

这就是我缺少的技巧;注意自我“如果要在可观察值发生变化时重新计算它们,则计算绝对应该访问可观察值”。这也是有道理的,为什么最初的开发人员会提出我以前认为是对 FormattedDuration 的非操作/毫无意义的调用(当时没有任何要格式化的东西)——通过这样做,它创建了对它使用的 observables 的订阅.谢谢

以上是关于为啥以这种方式更改此格式化函数会导致 Knockout 绑定开始工作?的主要内容,如果未能解决你的问题,请参考以下文章

反应JS |为啥在输入更改时更新此状态挂钩会清除输入以防止写入任何内容?

为啥启用透明度会导致剪裁问题?

为啥此代码会导致分段错误错误?

为啥 Node.js 会以这种方式执行?

为啥 OCaml 中的模块类型注释会导致此代码无法编译?

为啥 cProfile 会导致函数返回不同的值?