【问题标题】:Knockout: extend computed to deal with different observables淘汰赛:扩展计算以处理不同的可观察对象
【发布时间】:2016-06-12 22:27:36
【问题描述】:

我有一个计算出的淘汰赛,其工作方式类似于选中/取消选中除“是”和“否”单选按钮之外的所有字符串。

我想尝试扩展它,以便它可以针对不同的可观察属性工作。目前,如果“optionList”中的所有“paramOne”属性都被选为“yes”,那么我返回“yes”,类似于“no”。但我也想将它用于“paramTwo”参数和“paramThree”参数等。

任何想法如何做到这一点?也许计算在这里不是最合适的?

在这里提琴:https://jsfiddle.net/3kpx5qaf/

var AppViewModel = function() {
    var self = this;

  self.optionList = ko.observableArray();
  self.optionList().push({
    id : 'option1',
    title: "option one",
    paramOne:ko.observable(),
    paramTwo: ko.observable(),
    paramThree: ko.observable(),
    paramFour: ko.observable()
  });
  self.optionList().push({
    id : 'option2',
    title: "option two",
    paramOne:ko.observable(),
    paramTwo: ko.observable(),
    paramThree: ko.observable(),
    paramFour: ko.observable()
  });


  self.selectAll = ko.computed({
      read: function() {

      var numNo = 0;
      var numYes = 0;
      var items = self.optionList();

      ko.utils.arrayFirst(items, function(item) {
          if(item.paramOne() == "No"){
              numNo += 1;
          }else if(item.paramOne() == "Yes"){
              numYes += 1;
          }
      });

      if(numNo == items.length){
          return "No";
      }else if(numYes == items.length){
          return "Yes";
      }else{
          return null;
      }
  },
  write: function(value) {

      var items = self.optionList();

      ko.utils.arrayForEach(items, function(item) {
          item.paramOne(value);
      });
      }
  });
}

ko.applyBindings(new AppViewModel());

【问题讨论】:

  • 使用"yes""no" 而不是truefalse 有什么意义?使用两个字符串来表示布尔值很麻烦、容易出错、存在国际化问题以及所有这些,同时没有提供任何功能优势。那为什么要这么做呢?
  • 同意,布尔值更适合这里,但淘汰文档中的示例基于单选按钮值 (knockoutjs.com/documentation/checked-binding.html),所以这是我做的第一件事。
  • 我同意 Jeroen 关于 XY 问题的观点。与其描述您想要实施的解决方案,不如描述您想要解决的问题。

标签: knockout.js


【解决方案1】:

您很可能遇到了 XY 问题。你甚至通过问“X 不是最适合这里吗?”来暗示自己。但是,您没有给出任何上下文或 真实 示例(我希望您的真实代码没有“ParamOne”等变量?)。所以很难提出更好的解决方案。

您所问的关于您目前采用的方法的问题可以解释为:

我怎样才能干掉computed 函数,以便它们可以重复用于paramTwo 等?

您可以通过提取那段代码来这样做。这是一个仅使用 javascript 的简单示例:

var AppViewModel = function() {
  var self = this;

  // ...code omitted for brevity...

  function getReadFn(propertyName) {
    return function() {

      var numNo = 0;
      var numYes = 0;
      var items = self.optionList();

      ko.utils.arrayFirst(items, function(item) {
        if (item[propertyName]() == "No") { numNo += 1; }
        else if (item[propertyName]() == "Yes") { numYes += 1; }
      });

      if (numNo == items.length) { return "No"; }
      else if (numYes == items.length) { return "Yes"; }
      else { return null; }
    };
  }

  function getWriteFn(propertyName) {
    return function(value) {
      var items = self.optionList();

      ko.utils.arrayForEach(items, function(item) {
          item[propertyName](value);
      });
    };
  }

  self.selectAll_paramOne = ko.computed({
    read: getReadFn("paramOne"),
    write: getWriteFn("paramOne")
  });

  self.selectAll_paramTwo = ko.computed({
    read: getReadFn("paramTwo"),
    write: getWriteFn("paramTwo")
  });

  //etc.
}

或者,您也可以create your own extender 来封装逻辑。

但是,请再次仔细评估您的方法。具体来说:

  • “是”/“否”不是真正的本地化问题吗,您的支持 observable 不应该是布尔值吗?
  • 您的 paramOne/etc 可观察对象实际上不应该是(可观察的)列表或简单的数组吗?
  • 您不能通过使用mapsomeevery 等数组函数来大大简化您的函数吗? (这在一定程度上取决于您需要支持的浏览器/您可以使用的 polyfill)

【讨论】:

  • 同意布尔值(见我上面的评论)。我还给属性基本名称 (paramOne) 只是因为我的真实示例需要更多解释 - 尽管这样更容易。可观察对象是普通属性而不是可观察对象是可以的,因为它们仅受 UI 影响。我需要支持 IE8,所以虽然简化数组函数可能是可能的,但我还没有看过这个。
  • 在这种情况下,我坚持我的回答 :-)
  • @user2424495 为了支持 IE8,我建议包括 Sugar.js 库,它不仅可以填补缺少的数组方法,还添加了许多其他非常有用的实用程序。
【解决方案2】:

根据您的问题,如果选项的所有参数均相同 "yes""no" - 或 null(如果存在不匹配),我推测您想要一个变为 "yes""no" 的属性。

由于没有更好的名称,我们称该属性为overallSelection。由于它的值取决于一堆 observables,因此使用 ko.computed 来完成这项工作是很自然的。

您的示例应用程序由两部分组成 - 一个 Option,它有几个参数,一个 OptionList,即一个选项容器。这是两个视图模型,实现如下(通过单击下面的按钮运行代码):

function Option(data) {
    var self = this;

    self.paramNames = ["paramOne", "paramTwo", "paramThree", "paramFour"];
    self.id = data.id;
    self.title = data.title;
    self.paramNames.forEach(function (paramName) {
        self[paramName] = ko.observable();
    });

    self.overallSelection = ko.pureComputed(function () {
        var ref;
        self.paramNames.forEach(function (paramName, i) {
            if (i === 0) ref = self[paramName]();
            else if (ref !== self[paramName]()) ref = null;
        });
        return ref;
    });
}
function OptionList(data) {
    var self = this;

    self.options = ko.observableArray(data.options.map(function (data) {
        return new Option(data);
    }));
}

// -------------------------------------------------------------------------
var vm = new OptionList({
    options: [
        {id: 'option1', title: 'option one'},
        {id: 'option2', title: 'option two'}
    ]
});
ko.applyBindings(vm);
ul {
  list-style: none;
  padding: 0;
}
.param {
  display: inline-block;
  width: 6em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<ul data-bind="foreach: options">
  <li>
    <h4 data-bind="text: title"></h4>
    <ul data-bind="foreach: paramNames">
      <li>
        <span data-bind="text: $data" class="param"></span>
        <label><input type="radio" value="yes" data-bind="checked: $parent[$data]"> Yes</label>
        <label><input type="radio" value="no" data-bind="checked: $parent[$data]"> No</label>
      </li>
    </ul>
    <b class="param">Overall</b> <span data-bind="text: overallSelection"></span>
  </li>
</ul>

注意事项:

  • 目前这使用"yes""no",但如cmets 中所示,我不推荐该变体。通过从value 属性切换到checkedValue 绑定(the docs on the checked binding 的一部分),可以轻松更改为实际的布尔值。
  • 我的overallSelection 计算返回任何值,只要所有涉及的参数共享它。如果您切换到布尔值,则无需更改代码。
  • 整个事情的设计方式是将数据向下传递到 viewmodel 构造函数中。以这种方式设计视图模型很有用,它增加了它们的灵活性。
  • 这使用了现代浏览器中可用的数组原型函数。要同时支持新旧浏览器,要么从 ko.utils 切换到等效的浏览器,要么使用 polyfills(我倾向于推荐 Sugar.js)。
  • 一般提示:命名很难。尽量不要将selectAll 之类的名称命名为实际上并非“全选”的事物。

【讨论】:

    猜你喜欢
    • 2016-11-09
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    • 2014-08-08
    • 2014-04-24
    • 2017-09-19
    • 2023-03-20
    相关资源
    最近更新 更多