Knockout.js:在可选定义的值上计算可观察性

Knockout.js: computed observables on optionally defined values

本文关键字:计算 可观察性 定义 js Knockout      更新时间:2023-09-26

我为Yesod(一个Haskell web框架)编写了一个整洁的小knockout.js绑定,但在处理可选定义的值时遇到了一些问题。绑定的Haskell部分旨在提供一个javascript片段,该片段对同一处理程序进行Ajax调用,并接收通过ko.mapping解析的JSON对象。

这一切都很好,但我在处理可选值时遇到了问题。如果我用一个可选的记录提供一个值,JSON发射器会将其视为可选的,并且不发射它

例如,请求的JSON响应

data Foo = Foo { bar :: Maybe Int 
               , baz :: Int
               }
serveThis :: Foo
serveThis = Foo (Nothing) 0

是{baz:"0"}

我理解为什么会这样,我可以改变它(但如果可能的话,我宁愿不改变)。问题是,当我在serveThis的JSON表示上调用ko.mapping.fromJS时,bar字段不会变成可观察的。好吧,我也明白。我可以使用with绑定来进行条件数据绑定。但我不知道足够的JavaScript来有条件地定义计算的可观察性。

我的真实代码看起来像:

var ViewModel = function (data) {
  ko.mapping.fromJS(data, {}, this);
  this.notes.stdDev.percent = ko.computed( function () {
    return numeral( this.notes.stdDev() ).format("0%");
  }, this); 
}

因此,如果标准偏差没有在Haskell端定义,它就不会作为JSON字段发出,因此ko.mapping不会使其成为可观察的。那么,我如何有条件地定义百分比表示呢?

在省略stdDev属性的实例中,视图模型也会省略该属性,这是合适的行为吗?或者,无论初始值如何,您的视图模型是否需要包含属性的相关可观察对象?

如果是前者,那么你只需要包括一些类似于以下的简单条件逻辑:

this.notes.stdDev.percent = ko.computed(function() {
    return this.notes.stdDev ? this.notes.stdDev * 100 + "%' : 'n/a';
}, this);

这将检查是否存在stdDev属性,并在未定义该属性时返回静态值'n/a'

但是,如果您的视图需要所有用户输入stdDev值,即使省略了初始值,我也不建议使用此模型。相反,你可能想考虑放弃ko.mapping。大约一年前,我广泛使用了ko.mappiing,但最近我很少使用它,因为我使用JS模型得到了更好的结果。这里有一个视图模型体系结构的例子,它最近一直在为我工作。

var viewModel = (function () {
    return { init: init };
    function init(data) {
        var self = {
            notes: ko.observable()
        };
        self.notes(new Notes(data));
        ko.applyBindings(self);
        return self;
    }
    function Notes(data) {
        return {
            stdDev: ko.observable(data.notes),
            otherProp: ko.observable(data.otherProp),
        }
    }
}());

至少在映射讨论的上下文中,需要注意的主要内容是Notes构造函数。虽然可能会复制从服务层返回的模型,但与映射解决方案相比,这种模式允许可靠的数据结构。在决定它不适用于所有情况之前,我与映射进行了长达6个月的斗争,这种明确的模型定义确实为视图模型增加了清晰度和可靠性。尽管映射可以简化管道代码,但它确实对传入数据的结构产生了不必要的依赖。事实仍然是,如果视图引用了视图模型中不存在的属性,则视图将崩溃。

如果您确实想避免在两个层之间复制模型,那么有一个折衷的解决方案,您可以为所依赖的属性定义默认值,并在缺少默认值时使用默认值扩展传入数据模型。jQuery的扩展函数适用于这个函数——如果您已经依赖jQuery的话。例如:

var noteDefaults = {
    stdDev: 0
};
var input = $.extend({}, defaults, data);
ko.mapping.fromJS(input, {}, this);

希望这能有所帮助!