KnockoutJS绑定时源为null/undefined

45

有没有更短、更简单的方法来进行null/undefined测试?

<select data-bind="options: SelectedBusinessLine() ? SelectedBusinessLine().Clusters() : [],
                               optionsText: 'Title',
                               value: SelectedCluster,
                               optionsCaption: 'Select Cluster..'">
            </select>

不是

data-bind="options: SelectedBusinessLine() ? SelectedBusinessLine().Clusters() : [],

我想要

data-bind="options: SelectedBusinessLine().Clusters(),

大约 () 左右

或者至少一个更简单的 null 操作符 '??' SelectedBusinessLine ?? []

或者使用绑定参数来自动检查 null 值或者静默失败。

有没有任何想法,这是否可能实现?

6个回答

78

这个页面提供了几种解决方案。相关部分是:

防止 null 对象

如果您有一个包含对象的可观察对象,并且您想要绑定到该对象的属性,那么如果存在为 null 或未定义的情况,则需要小心。您可以编写如下绑定:

<span data-bind="text: selectedItem() ? selectedItem().name() : 'unknown'"></span>

有多种处理此问题的方法。首选方式是简单地使用模板绑定:

var viewModel = {
  items: ko.observableArray(),
  selectedItem: ko.observable()
};

<ul data-bind="template: { name: 'editorTmpl', data: selectedItem }"></ul>
<script id="editorTmpl" type="text/html">
  <li>
    <input data-bind="value: name" />
  </li>
</script>

使用这种方法,如果selectedItem为空,则不会渲染任何内容。因此,您将不会像在原始绑定中那样看到未知的结果。但是,它有一个额外的好处,可以简化绑定,因为现在您可以直接指定属性名称,而不需要selectedItem().name。这是最简单的解决方案。

只是为了探索一些选项,这里有几个替代方案:

您可以像以前一样使用计算可观察对象。

viewModel.selectedItemName = ko.computed(function() {
  var selected = this.selected();
  return selected ? selected.name() : 'unknown';
}, viewModel);

然而,这会给我们的视图模型增加一些臃肿的代码,而且我们可能需要为许多属性重复这个过程。

你可以使用自定义绑定,比如:

<div data-bind="safeText: { value: selectedItem, property: 'name', default: 'unknown' }"></div>

ko.bindingHandlers.safeText = {
  update: function(element, valueAccessor, allBindingsAccessor) {
    var options = ko.utils.unwrapObservable(valueAccessor()),
    value = ko.utils.unwrapObservable(options.value),
    property = ko.utils.unwrapObservable(options.property),
    fallback = ko.utils.unwrapObservable(options.default) || "",
    text;

    text = value ? (options.property ? value[property] : value) : fallback;

    ko.bindingHandlers.text.update(element, function() { return text; });
  }
};

这个方案比原来的好吗?我认为可能不是。它确实避免了我们绑定中的JavaScript,但仍然相当冗长。

另一个选项是创建一个增强型的observable,提供一种安全访问属性的方式,同时仍允许实际值为空。可以像这样:

ko.safeObservable = function(initialValue) {
  var result = ko.observable(initialValue);
  result.safe = ko.dependentObservable(function() {
    return result() || {};
  });

  return result;
};

所以,这只是一个可观察对象,同时还公开了一个计算的可观察对象,名为safe,它将始终返回一个空对象,但实际的可观察对象仍然可以存储null。

现在,你可以像这样绑定它:

<div data-bind="text: selectedItem.safe().name"></div>

selectedItem为null时,您将看不到未知值,但它至少不会导致错误。

我认为,在这种情况下,首选选项是使用模板绑定,特别是如果您有许多这些属性要绑定。


1
模板的唯一问题是有时我想显示一个没有选项的选择框而不是隐藏它。我认为框架应该为我们提供一种方法,使我们不必用 if null else 代码来膨胀源代码。 - Narcis
4
这里提供的模板示例实际上是无法正常工作的,如果模板中的"data"部分为空,则会出现绑定错误。通过在模板绑定中应用"if"属性,我可以解决这个问题:<div data-bind="template: { if: item(), data: item(), name: 'someTemplate' }"></div>。 - Drew Miller
@DrewMiller,我认为是jQuery模板引擎处理了空对象的处理。当使用本机knockout模板引擎时,if属性就可以解决问题。 - Boris Callens
只是提醒一下,ko.dependentObservable现在已经被ko.computed所取代。详见:http://knockoutjs.com/documentation/dependentObservables.html - uKolka
请参见下面的“with”作为最佳答案。 - Alexander K

64

在另一个答案提到的优秀页面中未提及的一种方法是使用with

<div data-bind="with: selecteditem">
    <form>
        <fieldset>
            <div>
                <label>first name</label>
                <input data-bind="value: firstname"></input>
            </div>
            <div>
                <label>lasst name</label>
                <input data-bind="value: lastname"></input>
            </div>
        </fieldset>
        <div>
            <a href="#" data-bind="click: $root.savechanges">Save</a>
        </div>
    </form>
</div>

如果 selecteditem 为 null,则整个 UI 将消失。


1
最简单、最优雅。谢谢! - Csaba Toth
2
这应该是被接受的答案。当前被接受的答案过于复杂,无法满足如此简单的要求。这是“with”语句的核心特性之一。 - julealgon
1
如果您需要访问上面的“作用域”,请使用$parent - Simon_Weaver
即使使用最新版本的所有内容,我发现“with binding”仍然会阻止自定义bindingHandlers进行绑定。 - Beej
1
@Simon_Weaver 我本来想避免使用 if not: VM 为空的绑定,也没有显式定义 VM(因为它有很多属性),但仅仅使用 mapping.fromJS 就足够了。 - Nick
显示剩余3条评论

4
"with"是有效的(其他也可能有效)。
但使用"with"时,即使存在条件,UI角色也会消失/出现...例如...我需要设置一个状态(真/假)按钮,但仅在状态不为空时才需要设置。
<!-- ko ifnot: Status() == null -->
<span data-bind="if: Status">
    <a class="rk-button rk-red-action" data-bind="click: changeStatus"><i class="rk-ico rk-ico-save"></i>Desativar</a>
</span>
<span data-bind="ifnot: Status">
    <a class="rk-button rk-black-action" data-bind="click: changeStatus"><i class="rk-ico rk-ico-save"></i>Ativar</a>
</span>
<!-- /ko -->

这有效。在这种情况下。
但有时只需像Simon_Weaver说的那样使用“With”!

1
我以前从未听说过 ifnot :-) - Simon_Weaver
如果您的“Status”可观察对象是计算的可观察对象,并且您还启用了ko.options.deferUpdates = true;,请对此保持谨慎,因为您仍然可能会遇到空值错误 - https://dev59.com/wqDia4cB1Zd3GeqPJc3_ - Simon_Weaver

2

以上大部分解决方案对我来说并不是完全适用,因为我只想将其应用于foreach中的单个元素,所以我稍微修改了被接受的答案的方法:

<span data-bind="text: ((typeof info).localeCompare('undefined')) ? info : ''"></span>

1
大多数解决方案对于我这种情况都不起作用,因为我正在设置一个属性:
<div role="combobox" data-bind="attr: {
  'aria-activedescendant': activedescendant() ? activedescendant().id : null
}">
  ...
</div>

templatewith 绑定在这里不起作用,因为如果我没有活动后代,则我的 div 将为空。对于我的解决方案,我创建了一个可观察的方法:

ko.observable.fn.get = function (propertyName, defaultValue) {
    var
    self = this(),
    propertyValue;

    if (self != null) {
        propertyValue = ko.unwrap(self[propertyName]);
    }

    return propertyValue || defaultValue;
}

这使我能够将我的绑定更改为:


<div role="combobox" data-bind="attr: {
  'aria-activedescendant': activedescendant.get('id')}">
  ...
</div>

1

我更喜欢这种方法

创建自定义绑定

ko.bindingHandlers.safeText = {
    update: function (element, valueAccessor, allBindingsAccessor) {
        try {
            var tryGetValue = valueAccessor()();
            ko.bindingHandlers.text.update(element, valueAccessor());
        } catch (e) {
            ko.bindingHandlers.text.update(element, function () { return ""; });
        }
    }
};

使用方法

data-bind="safeText: function() { return my().nested.object.property; }

你需要添加的唯一额外内容是用'function() { return ... }'包装你的原始值。

然而,这将停止值调用下面的所有错误。你可以通过仅查找'undefined'异常来改进自定义绑定。您还可以通过添加默认文本选项来改进此绑定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接