KnockoutJS - 双向绑定适配器 - 避免循环

时间:2017-09-02 12:33:11

标签: javascript knockout.js data-binding

在淘汰赛中很多次我遇到以下情况:

我有一个observable,我想在observables A和B之间创建一个双向绑定适配器,即如果A改变,改变B,如果B改变,则改变A.

        +-------------+         +-------------+         +-------------+
        |     A       |  -----> |  Adapter    |  -----> |      B      |
        |  Observable |  <----- |             |  <----- |  Observable |
        +-------------+         +-------------+         +-------------+

首先,这似乎是一个“不要做”,因为这会创建一个循环依赖,但最终这正是将GUI元素绑定到observable时发生的情况。想象一下你有一个现有的绑定,但你想改变它的绑定结果,而不需要触及绑定本身。

让我们看一个例子(jsfiddle here):

HTML:

<body>
   <p data-bind="text: 'Value:' + val()"></p>
   <input type="text" data-bind="textInput: val"></input>
   <p data-bind="text: 'Value 2:' + val2()"></p>
   <input type="text" data-bind="textInput: val2"></input>
</body>

Javascript:

function ViewModel() {
  var self = this;

  this.val = ko.observable("");
  this.val2 = ko.observable("");

  this.val.subscribe(function () {
    console.log("VAL Changed!");
    self.val2(self.val().toUpperCase());   
  });

  this.val2.subscribe(function() {
    console.log("VAL2 Changed!");
    self.val(self.val2().toLowerCase());   
  });
}

ko.applyBindings(new ViewModel());

您会注意到,当您在第一个文本框中键入内容时,会触发一个循环:

  • 绑定更改val
  • 订阅val fires并更改val2
  • 订阅val2会触发并更改val
  • 敲除表示再次运行订阅val(循环检测)

结果是,如果您在第一个输入框中键入一个大写字母,它将立即转换为小写字母,由第二个订阅,反之亦然。

虽然在这个例子中看起来很不错,但它可能导致很难找到错误。解决问题的一个简单方法是,在绑定中有一个标志,当我们进入另一方的更新时,这将避免更新:

jsfiddle here

 ....
 var flag = false; 
  this.val.subscribe(function () {
    if (flag) return; 
    flag = true; 
    self.val2(self.val().toUpperCase());
    flag = false; 
  });

  this.val2.subscribe(function() {
    if (flag) return; 
    flag = true; 
    self.val(self.val2().toLowerCase());
    flag = false; 
  });
  ....

现在当你改变第二个输入时,它不会&#34;反击&#34;但只能向一个方向射击。

现在终于我的问题了:

  • 适配器是否是一个无效的用例,是否暗示了代码的概念性问题?

  • 你会如何防止这种情况发生?有一个像我的例子中的旗帜?也许使用throtteling?

2 个答案:

答案 0 :(得分:1)

我在类似的情况下使用了以下方法。我认为它很干净,您不必担心使用标志来管理变量的状态。

基本上,使用可写的observable,并为每个相互依赖的项创建一个后备observable来存储它的状态,然后使用writeable observable来处理逻辑,当该值发生变化时,对于其他observable会发生什么

所以,你的viewmodel看起来像这样:

function ViewModel() {
  var self = this;

  self.val = ko.observable("");
  self.val2 = ko.observable("");

  self.valComputed = ko.pureComputed({
    read: function () { return self.val(); },
    write: function (value) {
        self.val(value);
        self.val2(value.toUpperCase());
    }
  });

  self.val2Computed = ko.pureComputed({
    read: function () { return self.val2(); },
    write: function (value) {
        self.val2(value);
        self.val(value.toLowerCase());
    }
  });
}

ko.applyBindings(new ViewModel());

您可以更改HTML以绑定到计算的observable,如下所示:

<body>
   <p data-bind="text: 'Value:' + valComputed()"></p>
   <input type="text" data-bind="textInput: valComputed"></input>
   <p data-bind="text: 'Value 2:' + val2Computed()"></p>
   <input type="text" data-bind="textInput: val2Computed"></input>
</body>

我希望有所帮助! : - )

答案 1 :(得分:1)

您拥有的是一个实际数据项和两个计算结果,以将其显示为大写或小写。诀窍是计算机需要是可写的。他们的写函数可以直接写入底层数据项,因为它的情况并不重要。

function ViewModel() {
  var self = this;

  this.val = ko.observable("");

  this.val1 = ko.pureComputed({
    read: function() {
      return self.val().toLowerCase();
    },
    write: function(newVal) {
      self.val(newVal);
    }
  });
  this.val2 = ko.pureComputed({
    read: function() {
      return self.val().toUpperCase();
    },
    write: function(newVal) {
      self.val(newVal);
    }
  });

  this.val1.subscribe(function() {
    console.log("VAL Changed!");
  });

  this.val2.subscribe(function() {
    console.log("VAL2 Changed!");
  });
}

ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<body>
  <p data-bind="text: 'Value:' + val1()"></p>
  <input type="text" data-bind="textInput: val1" />
  <p data-bind="text: 'Value 2:' + val2()"></p>
  <input type="text" data-bind="textInput: val2" />
</body>