【问题标题】:Knockout observable incorrectly changing result of a math calculation when value is input输入值时,Knockout observable 错误地改变了数学计算的结果
【发布时间】:2019-10-21 14:20:29
【问题描述】:

总结:当一个方程使用的可观察值是由用户输入而不是通过另一个变量设置时,它正在改变它的输出。方程的输出也会被方程的阶数修改,但仅在用户输入值时。

我有一个如下公式:

            var numMinions = Math.floor(
              customDifficultySettings.mandatoryMinions() +
                worker.star.distance() * customDifficultySettings.minionMod()
            );

worker.star.distance() 正在从星的信息数组中提取一个值,出于本问题的目的,应将其忽略。它将返回一个整数。

通过一系列困难将小兵的值输入其中:

var difficultyInfo = [
      {
        // Easy
        customDifficulty: false,
        minionMod: 0.2,
        mandatoryMinions: 1
      },
      // more difficulty levels
      {
        // Custom
        customDifficulty: true,
      }

稍后更新 observable 以匹配选择:

      if (
        !difficultyInfo[model.newGameDifficultyIndex() || 0].customDifficulty
      ) {
        customDifficultySettings.mandatoryMinions(
          difficultyInfo[model.newGameDifficultyIndex() || 0].mandatoryMinions
        );
        customDifficultySettings.minionMod(
          difficultyInfo[model.newGameDifficultyIndex() || 0].minionMod
        );
      }
      // Used to hide custom input fields if not the custom difficulty level
      if (
        difficultyInfo[model.newGameDifficultyIndex() || 0].customDifficulty
      ) {
        customDifficultySettings.customDifficulty(true);
      } else {
        customDifficultySettings.customDifficulty(false);
      }

这个视图模型如下:

var customDifficultySettings = {
  mandatoryMinions: ko.observable(),
  minionMod: ko.observable(),

如果选择了“自定义”难度,则会公开字段以允许用户通过字段更新值:

document
  .getElementById("game-difficulty")
  .insertAdjacentHTML(
    "afterend",
    '<div class="sub_options" id="custom-difficulty-settings" data-bind="visible: customDifficultySettings.customDifficulty()">' +
      '<div class="form-group">' +
      '<div><input type="number" style="width: 50px; padding-bottom: 0px;" data-bind="textInput: customDifficultySettings.mandatoryMinions" />' +
      '<span style="margin-left: 6px;"></span><loc>Mandatory Minions</loc></label>' +
      '<span class="info_tip" data-bind="tooltip: \'!LOC:Number of additional Commanders in every system.\'">?</span></div>' +
      '<div><input type="number" style="width: 50px; padding-bottom: 0px;" data-bind="textInput: customDifficultySettings.minionMod" />' +
      '<span style="margin-left: 6px;"></span><loc>Minion Modifer</loc></label>' +
      '<span class="info_tip" data-bind="tooltip: \'!LOC:Mandatory Minions + Star Distance * Minion Modifier = number of additional enemy Commanders.\'">?</span></div>' +
      "</div></div>"
  );

例如,当customDifficultySettings.mandatoryMinions = 1customDifficultySettings.minionMod = 0 时,我期望numMinions = 1。对于从 diffInfo 数组中提取值的每个困难,这都能按预期工作。

但是,如果我选择自定义难度并在 Mandatory Minions 字段中输入数字 1,并且 Minion Modifier 为 0,那么我会得到 numMinions = 10。如果我将等式更改为:

            var numMinions = Math.floor(
              worker.star.distance() * customDifficultySettings.minionMod() +
                customDifficultySettings.mandatoryMinions()
            );

然后输入相同的值,我得到numMinions = 01

我假设我输入的输入不正确,并且在通过字符串输入时它没有作为数字返回,但我不确定应该如何修改输入以更正此问题。

我在这方面不知所措。这是我第一次使用 Knockout.js。

【问题讨论】:

    标签: javascript knockout.js


    【解决方案1】:

    好的,“快速补丁”就是将变量转换为数字,因为看起来 observable 实际上有一个字符串,而不是数字。这意味着像 '1'+'0'='10' 这样的字符串是纯字符串 concat 的结果。 所以可以先将数字解析为Number():

    var numMinions = Math.floor(
              Number(customDifficultySettings.mandatoryMinions() || 0) +
                worker.star.distance() * customDifficultySettings.minionMod()
            );
    

    我将其称为快速补丁,因为我并不真正意识到此片段在您的流程中执行有多“晚”。正确解决此问题的最佳方法是尽可能高地解析数字,以确保在此之前的其他部分也不会受到影响

    【讨论】:

    • 非常感谢您的回复。我今天早些时候终于弄清楚了,并发布了我选择的答案。
    【解决方案2】:

    您需要在计算中使用parseFloat

    var ViewModel = function () {
        var self = this;
        
        this.firstValue = ko.observable(0);
        this.lastValue = ko.observable(0);
        
        this.fullValue = ko.computed(function() {
          var firstValue = self.firstValue();
          var lastValue = self.lastValue();
          return parseFloat(firstValue) + parseFloat(lastValue);
        });
    };
    
    ko.applyBindings(new ViewModel());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <html>
    
    <body>
      <p>
        <label for="value-first">First Value</label>
        <input id="value-first" type="text" data-bind="textInput: firstValue" />
      </p>
      <p>
        <label for="value-last">Last Value</label>
        <input id="value-last" type="text" data-bind="textInput: lastValue" />
      </p>
      <p>
        <label for="value-full">Full Value</label>
        <input id="value-full" type="text" data-bind="textInput: fullValue" />
      </p>
    </body>
    
    </html>

    此外,我建议您还使用默认值初始化 observables。

    您还可以为您的文本框使用自定义的纯数字解决方案:Number input box in Knockout JS

    【讨论】:

    • 非常感谢您的回复。我今天早些时候终于弄清楚了,并发布了我选择的答案。
    【解决方案3】:

    您可以使用扩展器来确保您的 observables 始终是数字的。 IMO,这比订阅好一点。

    ko.extenders.numeric = function(target, precision) {
        //create a writable computed observable to intercept writes to our observable
        var result = ko.pureComputed({
            read: target,  //always return the original observables value
            write: function(newValue) {
                var current = target(),
                    roundingMultiplier = Math.pow(10, precision),
                    newValueAsNum = isNaN(newValue) ? 0 : +newValue,
                    valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;
    
                //only write if it changed
                if (valueToWrite !== current) {
                    target(valueToWrite);
                } else {
                    //if the rounded value is the same, but a different value was written, force a notification for the current field
                    if (newValue !== current) {
                        target.notifySubscribers(valueToWrite);
                    }
                }
            }
        }).extend({ notify: 'always' });
    
        //initialize with current value to make sure it is rounded appropriately
        result(target());
    
        //return the new computed observable
        return result;
    };
    

    (这个其实是取自docs!)

    然后你会像这样定义你的虚拟机:

    var customDifficultySettings = {
      mandatoryMinions: ko.observable().extend({ numeric: 0 }),
      minionMod: ko.observable().extend({ numeric: 0 }),
    

    0 更改为您想要的任意位数。

    【讨论】:

      【解决方案4】:

      我发现 observables 正在返回字符串,这导致添加只是简单地连接它们。我在视图模型的声明下添加了以下内容:

      customDifficultySettings.mandatoryMinions.subscribe(function(value) {
        customDifficultySettings.mandatoryMinions(
          Math.max(0, Number(Number(value).toFixed(0)))
        );
      });
      customDifficultySettings.minionMod.subscribe(function(value) {
        customDifficultySettings.minionMod(
          Math.max(0, Number(Number(value).toFixed(2)))
        );
      });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多