Knockout 似乎不喜欢 object.property in bind

Knockout doesn't seem to like object.property in bind

在我的 Knockout.js 模板中,如果我可以访问视图模型上对象的属性会很方便:

<span data-bind="text: account.shortName"></span>

这行不通。该元素为空。但是,我可以这样做:

<div data-bind="with: account">
  <span data-bind="text: shortName"></span>
</div>

有什么办法解决这个问题吗?一定要到处都用with吗,多余的元素也一样吗?

如果 account 是可观察的,那么您真的应该像已经拥有的那样使用 with 绑定,或者使用计算的可观察对象来访问 属性。当然,它有点冗长,但必须这样做。

使用像 someObservable().someProperty 这样的表达方式只会导致头痛和困惑,应该避免。例如,如果你确实使用了这个并且 someProperty 恰好是可见的,你可能会注意到当 someObservable 发生变化时有些不对劲。绑定将不会更新为使用新值的someProperty,我希望你能明白为什么。

您可以通过创建一个函数来更轻松地以安全的方式创建计算可观察对象。

ko.observable.fn.property = function (name) {
    return ko.computed({
        read: function () {
            var parentValue = this();
            if (parentValue)
                return ko.utils.unwrapObservable(parentValue[name]);
        },
        write: function (value) {
            var parentValue = this(), property;
            if (parentValue) {
                property = parentValue[name];
                if (ko.isWriteableObservable(property))
                    property(value);
            }
        },
        owner: this
    });
};

然后你可以在你的绑定中使用它:

<span data-bind="text: account.property('shortName')"></span>

Jeff 的回答很好,但您也可以通过将绑定更改为:

text: account() && account().shortName

我发现,与 Jeff 在他的回答中提到的相反,即使 shortName 是可观察的,因为绑定是 implemented inside of a computed observable,这仍然有效。换句话说,text 绑定的值被实现为一种匿名计算。

这是一个显示它有效的片段,从一个没有值的 account observable 开始,然后随着时间的推移更新内容。

var vm = {
  account: ko.observable()
};

$(function() {
  ko.applyBindings(vm);

  // After a second, set account
  setTimeout(function() {
    var account = {
      shortName: ko.observable('Initial account')
    };
    vm.account(account);

    var newAccount = {
      shortName: ko.observable('New account')
    }

    // After another second, change account
    setTimeout(function() {
      vm.account(newAccount);

      // After another second, change shortName within the new account
      setTimeout(function() {
        newAccount.shortName('New shortName value in the new account');
      }, 1000);
    }, 1000);
  }, 1000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<body>
  <h1>Account Binding Test</h1>
  <span data-bind="text: account() && account().shortName"></span>
</body>