【问题标题】:KnockoutJS - two way binding adapter - avoid cyclesKnockoutJS - 双向绑定适配器 - 避免循环
【发布时间】:2018-02-11 07:42:46
【问题描述】:

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

我有一个可观察对象,我想在可观察对象 A 和 B 之间创建一个双向绑定适配器,即如果 A 更改,则更改 B,如果 B 更改,则更改 A。

+-------------+ +-------------+ +--------------+ |一个 | -----> |适配器 | -----> |乙| |可观察 |

起初这似乎是一个不应该做的事情,因为这会创建一个循环依赖,但最终这正是当您将 GUI 元素绑定到可观察对象时发生的情况。想象一下,您有一个现有的绑定,但您想更改其绑定结果,而不触及绑定本身。

让我们看一个例子(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 触发并更改 val2
  • 订阅 val2 触发并更改 val
  • knockout 抑制再次运行 subscribe for 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; 
  });
  ....

现在,当您更改第二个输入时,它不会“回火”,而只会朝一个方向开火。

最后是我的问题:

  • 适配器是否是无效用例,是否暗示代码存在概念问题?

  • 您将如何防止循环?用我的例子中的标志?也许使用节流?

【问题讨论】:

    标签: javascript knockout.js data-binding


    【解决方案1】:

    您拥有的是一个实际数据项和两个计算值,以将其显示为大写或小写。诀窍是计算需要是可写的。他们的 write 函数可以直接写入底层数据项,因为它的大小写无关紧要。

    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>

    【讨论】:

    • 这并不能解决循环依赖的影响。如果你运行你的代码 sn-p 你会意识到,当在第二个输入中输入一个小写字母时,它会在第一个订阅返回时立即转换为大写字母。
    • 订阅不进行任何转换。没有循环。当基础数据项更改时,两个计算的 read 值都会更改,仅此而已。
    • 对不起 - 我没有花适当的时间来了解您的解决方案。所以你说有些情况下“适配器”的两端只代表“一个”值。上述解决方案有所不同,它允许适配器的两端具有不同的状态,而在您的情况下,适配器的两端只有一个状态“val”。在我看来,这比 Matthews 的解决方案更干净。您是否认为在某些情况下,具有 2 个状态的解决方案可能更适用?我的大脑还不能完全围绕它。
    • 作为一般原则,您不应该有多余的状态。如果两件事一起移动,它们是具有不同表示的共享状态,其中一个应该是计算的。 Here is an example 两个状态加上一个计算模型三个相互关联的变量。
    【解决方案2】:

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

    基本上,利用可写可观察对象,并为每个相互依赖的项目创建一个支持可观察对象来存储其状态,然后使用可写可观察对象来处理当该值发生变化时其他可观察对象应该发生什么的逻辑.

    因此,您的视图模型将如下所示:

    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 来绑定到计算的 observables,像这样:

    <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>
    

    希望对您有所帮助! :-)

    【讨论】:

    • 啊啊我的脑痛... :-) 与真实可观察对象的行为差异在于写入 val2Computed 不会反映在其自身的值中。你认为添加'self.val2(value);'会节省吗?到 val2Computed 的写处理程序以及 'self.val(value);'到 valComputed 的 write Handler 以确保计算对象在绑定到它们时会表现得像真正的可观察对象?见this fiddle
    • 啊,是的,当然。接得好!您肯定希望存储正在设置的可观察对象的值。我编辑了它,添加了这些行。
    猜你喜欢
    • 2019-08-24
    • 1970-01-01
    • 2018-10-07
    • 1970-01-01
    • 2013-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多