knockout.js 在 ko.computed 写入中取消 confirm() 后如何设置所选选项

knockout.js how to set selected option after confirm() cancelled within a ko.computed write

我有一个带有选项和默认文本的 selector 元素:

self._selected = ko.observable();
self.option = ko.computed({
    read:function(){
        return self._selected;
    },
    write: function(data){
        if(data){
            if(confirm('are you sure?')){
                self._selected(data);
            }else{
                //reset
            }
        }
    }
});

<select data-bind="options: options, value:option, optionsCaption: 'choose ...'"></select>

这个问题:

应该是"choose ..."

jsbin here,仅在 chrome

上测试

问题是底层变量的值没有改变,所以没有事件告诉 Knockout 它的 value 与视图模型不同步。

对于普通的可观察对象,您可以调用 valueHasMutated 来指示发生了一些神秘的变化,但计算似乎没有。但他们确实有notifySubscribers。事实上,你的例子很像 this example in the docs.

这是一个工作示例:

function vm() {
  const self = {};

  self.options = ko.observableArray(['one', 'two', 'three']);
  self._selected = ko.observable();
  self.option = ko.pureComputed({
    read: self._selected,
    write: function(data) {
      if (data) {
        if (confirm('are you sure?')) {
          self._selected(data);
        } else {
          self.option.notifySubscribers(self._selected());
        }
      }
    }
  });

  return self;
}

ko.applyBindings(vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: options, value:option, optionsCaption: 'choose ...'"></select>
<div data-bind="text:_selected"></div>
<div data-bind="text:option"></div>

这里有一个不对称:

当您更改 select 框的值时,DOM 会立即更新并随后被淘汰(当然,淘汰取决于 DOM 更改事件)。因此,当您的代码询问 "Are you sure?" 时,DOM 已经具有新值。

现在,当您 not 将该值写入绑定到 value: 的可观察对象时,视图模型的状态不会改变。并且 knockout 仅在可观察值发生变化时更新 DOM 。因此 DOM 保持在 selected 值,并且您的视图模型中的绑定值不同。


最简单的方法是将旧值保存在变量中,总是将新值写入可观察对象,如果用户单击则简单地恢复旧值"no"。这样就打破了不对称性,DOM 和视图模型保持同步。

var AppData = function(params) {
    var self = {};
    var selected = ko.observable();
    
    self.options = ko.observableArray(params.options);  
    self.option = ko.computed({
        read: selected,
        write: function(value) {
            var oldValue = selected();
            selected(value);
            if (value !== oldValue && !confirm('are you sure?')) {
                selected(oldValue);
            }
        }
    });
  
    return self;
};

// ----------------------------------------------------------------------
ko.applyBindings(new AppData({
  options: ['one','two','three']
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<select data-bind="options: options, value: option, optionsCaption: 'Select...'"></select>

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>


这是要求价值变更确认的淘汰扩展器的完美候选者。这样我们就可以将它重新用于不同的可观察对象并保持视图模型干净。

ko.extenders.confirmChange = function (target, message) {
    return ko.pureComputed({
        read: target,
        write: function(newValue) {
            var oldValue = target();
            target(newValue);
            if (newValue !== oldValue && !confirm(message)){
                target(oldValue);
            }
        }
    });
};

// ----------------------------------------------------------------------
var AppData = function(params) {
    var self = this;
    
    self.options = ko.observableArray(params.options);  
    self.option = ko.observable().extend({confirmChange: 'are you sure?'});
};

// ----------------------------------------------------------------------
ko.applyBindings(new AppData({
  options: ['one','two','three']
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<select data-bind="options: options, value: option, optionsCaption: 'Select...'"></select>

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>