【问题标题】:Knockout: doing a push into an observable array makes each item's computed observable to be executedKnockout:对 observable 数组进行 push 使得每个 item 的计算 observable 被执行
【发布时间】:2015-02-20 16:46:54
【问题描述】:

在下面的代码中,我将一个项目添加到可观察数组中。这会导致所有先前的项目在执行 .push 时执行其计算的 observable。这是不必要的。如何防止这种情况? 我拥有的项目越多,这意味着计算出的每个项目 observable 都会运行。

参见小提琴示例。 http://jsfiddle.net/k8kbdnnv/3/

html代码

    <div id="logs"></div>
<table>  

<tbody data-bind="foreach: CompanySearchRows">
                <tr >
                    <td>
                        <input data-bind='textInput: ClientReference' type="text"  />

                    </td>
                    <td>
                        <input data-bind='textInput: CompanySearchReference' type="text"  />
                    </td>
                </tr>

            </tbody>
</table>

js代码

var viewModel;
var maxTitleRefRows = 50;

function ViewModel() {
    var self = this;
    self.CompanySearchRows = new ko.observableArray([new ItemViewModel()]);
    self.Executions = ko.observable(0);

    self.addRow = function (clientReference) {  


    var newRow = new ItemViewModel();       

    self.CompanySearchRows.push(newRow);  //this causes the code to jump 

into each array item's OnCompanySearchReferenceChanged computed observable 
    };
}


function ItemViewModel() {
    var self = this;   
    self.ClientReference = ko.observable(""); 
    self.CompanySearchReference = ko.observable(""); 

self.OnCompanySearchReferenceChanged = ko.computed(function () {               
    var reference = self.CompanySearchReference();

   logIt("computed executed" + reference);


    if (reference !== ""  ) {                    
        var currentIndex = viewModel.CompanySearchRows.indexOf(self);
        viewModel.Executions(currentIndex);
        var length = viewModel.CompanySearchRows().length;
        var lastItemIndex = length - 1;   
        if (currentIndex === lastItemIndex && length < maxTitleRefRows ) {          
            viewModel.addRow(self.ClientReference());               
        }
    }

    return;
});      
}

var logs=document.getElementById('logs');
function logIt(msg){
    var e=document.createElement('div');
    e.innerHTML=msg;
    logs.insertBefore(e,logs.firstChild);
}

$(document).ready(function () {
    viewModel = new ViewModel();
    ko.applyBindings(viewModel);    
});

【问题讨论】:

  • 您的计算函数包含对数组的引用。因此,如果数组发生变化(例如推送),它会触发对所有现有项目的重新计算。 var currentIndex = viewModel.CompanySearchRows.indexOf(self);我不太清楚这个函数是做什么的,所以我不能建议改变
  • 嗯,这可能是一个XY problem:您的computed 正在引起副作用并且没有返回任何有意义的结果。计算的 observables 用于读取(有时写入)视图模型上的属性。
  • 查看计算函数,我假设该项目正在尝试确定它是否是数组中的最后一项,如果是,插入一个新行?那正确吗。如果是这样,你可能把逻辑放在了错误的地方:让孩子控制父母通常是不好的(我说的是三个孩子的父母!)。我会看看我是否可以创建一个更好的版本,其中主 viewModel 处理添加新行,并且 ItemViewModel 可以忽略它的数组。
  • 是的,这就是计算在做什么。如果 companySearchReference 属性发生更改,则检查它是否是最后一行。如果是,则将新行插入父数组。

标签: knockout.js


【解决方案1】:

computed 函数包含对主 ViewModel 中observableArray 的引用。这意味着如果数组更改(例如,从 push所有所有项目上的计算函数都会重新评估。

阅读代码,计算函数的意图似乎是在第二个字段中输入值时添加一个新行。所以我们只需要跟踪 last 项就可以做到这一点。通常建议不要在子对象内部执行此操作 - 主视图模型应该管理数组,子对象应该独立于它。

为了实现这一点,我在CompanySearchReference observable 上使用了subscription ,但只在最后一项上。当该值发生变化时,父 ViewModel 将添加新行,同时删除上一行的订阅,因此在任何时候只有一个订阅处于活动状态。

// subscribe to changes on the child's search reference
// when this has a value, create a new row
var subscription = newRow.CompanySearchReference.subscribe(function (newValue) {
   if (newValue && self.CompanySearchRows().length < maxTitleRefRows) {
       // value has been changed: first remove this subscription
       logIt("removing subscription");
       subscription.dispose();
       // add a new row (creates a new subscription on new row)
       logIt("adding new row");
       self.addRow();
   }
});

完整的 JSFiddle 位于 http://jsfiddle.net/Quango/fgfw6Ld0/

【讨论】:

  • 我认为您的解决方案会奏效。如果有一个淘汰功能来阻止任何进一步的处决,那就太好了。
  • 您以错误的方式使用了计算值,所以这并不是淘汰赛的错。计算函数不打算作为临时对象。订阅(可以轻松关闭)是一种更合适的方式。
猜你喜欢
  • 2014-08-11
  • 2012-04-19
  • 2023-03-21
  • 2013-08-08
  • 2014-03-02
  • 2012-12-03
  • 1970-01-01
  • 1970-01-01
  • 2013-12-31
相关资源
最近更新 更多