【问题标题】:Circular dependency of knockout computed计算淘汰赛的循环依赖
【发布时间】:2017-04-08 03:55:32
【问题描述】:

查看工作中的 jsFiddle:http://jsfiddle.net/ruslans/vFK82/

我有 3 个字段:净价(不含税)、税额和总价(不含增值税的价格 + 税额)。 NetPrice 和 Total 是可写的,即您可以更改其中任何一个,而其他 2 个值必须自动计算。

我的做法是使用 3 个可观察对象和 2 个计算剔除对象,但我认为也许更了解 Knockout 的人可以提出一种更有效的方法来实现这一点。

html:

Net Price:
<input type="textbox" data-bind="value: NetPriceCalc" />
<br />Tax Amount:
<label data-bind="html: TaxAmt"></label>
<br />Total:
<input type="textbox" data-bind="value: TotalCalc" />

脚本:

var viewModel = {
    NetPrice: ko.observable(100),
    TaxAmt: ko.observable(20),
    Total: ko.observable(120),
    TaxRate: 0.2
};

viewModel.updateTaxAmt = function (useNetPrice) {
    if (useNetPrice) {
        return this.TaxAmt(this.NetPrice() * this.TaxRate);
    } else {
        var total = Number(this.Total());
        var taxAmt = total - total / (1 + this.TaxRate);
        return this.TaxAmt(taxAmt);
    }
};
viewModel.updateNetPrice = function () {
    this.NetPrice(Number(this.Total()) - Number(this.TaxAmt()));
};
viewModel.updateTotal = function () {
    this.Total(Number(this.NetPrice()) + Number(this.TaxAmt()));
};

viewModel.NetPriceCalc = ko.computed({
    read: function () {
        console.log("NetPriceCalc read");
        return viewModel.NetPrice();
    },
    write: function (value) {
        console.log("NetPriceCalc write");
        viewModel.NetPrice(value);
        viewModel.updateTaxAmt(true);
        return viewModel.updateTotal();
    }
});
viewModel.TotalCalc = ko.computed({
    read: function () {
        console.log("TotalCalc read");
        return viewModel.Total();
    },
    write: function (value) {
        console.log("TotalCalc write");
        viewModel.Total(value);
        viewModel.updateTaxAmt(false);
        return viewModel.updateNetPrice();
    }
});

ko.applyBindings(viewModel);

【问题讨论】:

    标签: javascript knockout.js


    【解决方案1】:

    OP 的一些 cmets:

    • 您不需要ko.computedwrite 方法中的return 子句。
    • 您的方法在多个位置使用Number() 函数,您可能希望更改它以获得特定的精度(或某个用于验证用户输入的集中位置)。所以你可以使用ko.extenders 来改进它。我特别推荐名为ko.extenders.numeric的ko团队已经制作的扩展器。
    • 您的方法还在多个地方使用了console.log(),您可能希望使用由 ko 团队 ko.extenders.logChange 制作的另一个 ko.extender。
    • 在这种情况下,我认为最好使用subscribe 而不是ko.computed,因为它需要更少的代码(并且可能更快)。

    我的方法是这样的:

    function viewModel() {
        this.TaxRate = 0.2;
        this.NetPrice = ko.observable().extend({ numeric: 2, logChange: "NetPrice"  });
        this.TaxAmt = ko.observable().extend({ numeric: 2, logChange: "TaxAmt"  });
        this.Total = ko.observable().extend({ numeric: 2, logChange: "Total"  });
    
        this.NetPrice.subscribe(function (newNetPrice) {
            this.TaxAmt(newNetPrice * this.TaxRate);
            this.Total(newNetPrice + this.TaxAmt());
        }, this);
        this.Total.subscribe(function (newTotal) {
            this.TaxAmt(newTotal - newTotal / (1 + this.TaxRate));
            this.NetPrice(newTotal - this.TaxAmt());
        }, this);
    
        this.NetPrice(100);
    }
    
    // then I have the extenders code copied exactly as seen in: http://knockoutjs.com/documentation/extenders.html)
    ko.extenders.numeric = ...
    ko.extenders.logChange = ... 
    
    // and finally init everything as usual
    ko.applyBindings(new viewModel());
    

    你可以在这里看到工作小提琴:http://jsfiddle.net/protron/JFPgu/2/

    请注意,此解决方案中的数字永远不会比数字扩展器上指定的小数位数多(即使用户输入的值也会自动固定为所需的精度)。

    并将我的答案与 gaurav 目前接受的答案进行比较(这也非常好和简单):我认为我的方法的主要优点是它可以让你使用这些很棒的扩展器。

    【讨论】:

    • 哦,extenders 非常酷,尤其是我可以将子可观察对象添加到现有可观察对象 - 我的用例是关于特定渲染 UI 片段的错误消息,来自不同的端点(不是我选择的 API 架构)。
    【解决方案2】:

    一种更好更有效的方法可能是这样的:

    Working Fiddle

    HTML

    Net Price:
    <input type="textbox" data-bind="value: NetPrice" />
    <br />Tax Amount:
    <label data-bind="html: TaxAmt"></label>
    <br />Total:
    <input type="textbox" data-bind="value: Total" />
    

    JS

    function viewModel() {
        var self = this;
    
        self.NetPrice = ko.observable(100);
    
        self.TaxRate = 0.2;
    
        self.TaxAmt = ko.computed(function() {
            return parseFloat(self.NetPrice()) * self.TaxRate;
        });
    
        self.Total = ko.computed({
            read: function() { 
                   return parseFloat(self.NetPrice()) + self.TaxAmt();
            },
            write: function(val){
                    var total = parseFloat(val);
                    var taxAmt = total - total / (1 + self.TaxRate);     
                    self.NetPrice(total - taxAmt);
            }
        });
    }
    

    希望对你有帮助!

    【讨论】:

    • 很酷。您可以将valueUpdate: 'afterkeydown' 添加到字段的数据绑定属性中,以便它们在每次击键时更新。
    【解决方案3】:

    不能保证这会解决问题,但有时启用延迟更新可以解决此类问题。

    这是一个需要注意的好功能,但在为已经运行的应用程序启用它时要谨慎 - 而且如果您确实有潜在问题,那么您仍然需要修复它。

    http://knockoutjs.com/documentation/deferred-updates.html

    【讨论】:

    • 注意:这与延迟的 observable 不同——它是从 3.4.0 开始的新功能
    猜你喜欢
    • 2013-06-10
    • 2019-10-03
    • 2012-06-12
    • 2017-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多