假设我使用的是一个API,它返回JSON数据,但具有复杂或可变的结构。例如,字符串值属性可以是纯文本,也可以使用以下语言进行标记:
/* first pattern */
{ "id": 1,
"label": "a foo"
}
/* second pattern */
{ "id": 2,
"label": [ {"value": "a foo", "lang": "en"},
{"value": "un foo", "lang": "fr"}]
}
在我的客户端代码中,我不想让视图代码担心标签是否有多种语言可用,以及选择哪种语言等。或者,我可能出于其他原因想要隐藏详细的JSON结构。因此,我可以使用合适的API将JSON值包装在对象中:
/** Value object for foo instances sent from server */
var Foo = function( json ) {
this.json = json;
};
/** Return a suitable label for this foo object */
Foo.prototype.label = function() {
var i18n = ... ;
if (i18n.prefLang && _.isArray(this.json.label)) // ... etc etc
};
所以这都是非常正常的值对象模式,它很有帮助,因为它与特定的JSON结构更解耦,更可测试,等等。好吧。
我目前看不到的是如何将这些值对象之一与Backbone和Marionette一起使用。具体来说,我想使用Foo
对象作为Backbone Model
的基础,并将其绑定到Marionette ItemView
。然而,就我所见,Model
中的值直接取自JSON结构——我看不出有什么方法可以识别对象是函数:
var modelFoo = new Backbone.Model( foo );
> undefined
modelFoo.get( "label" ).constructor
> function Function() { [native code] }
因此,我的问题是:什么是将Backbone Model
的属性与给定JSON结构(如复杂的API值)的细节解耦的好方法?有价值的对象、模型和视图可以玩得很好吗?
编辑
让我再加一个例子,因为我认为上面关注i18n问题的例子只传达了我的部分担忧。简单地说,在我的领域里,我有由河流、湖泊和潮间带组成的水体。水体有一个或多个采样点,每个采样点都有一个最新的样本。这可能是从服务器上的数据API返回的,类似于:
{"id": "GB12345678",
"centre": {"lat": 1.2345, "long": "-2.3456"},
"type": "river",
"samplingPoints": [{"id": "sp98765",
"latestSample": {"date": "20130807",
"classification": "normal"}
}]
}
因此,在我的视图代码中,我可以编写表达式,例如:
<%= waterbody.samplingPoints[0].latestSample.classification %>
或
<% if (waterbody.type === "river") { %>
但如果API格式改变,这将是可怕的,并且很容易被打破。稍微好一点的是,我可以将这样的操作抽象到模板助手函数中,但它们仍然很难编写测试。我想做的是有一个值对象类Waterbody
,这样我的视图代码就可以有这样的东西:
<%= waterbody.latestClassification() %>
我发现Marionette的一个主要问题是坚持对传递给视图的模型调用toJSON()
,但也许一些计算的属性建议可以绕过这一点。
IMO最干净的解决方案是将标签访问器而不是VO:放入模型中
var FooModel = Backbone.Model.extend({
getLabel : function(){
return this.getLocalized("label");
},
getLocalized : function(key){
//return correct value from "label" array
}
});
并让视图使用FooModel#getLabel
而不是FooModel#get("label")
--编辑1
这个库对于您的用例来说似乎也很有趣:Backbone.Schema
它允许您正式声明模型属性的类型,但也为本地化字符串提供了一些语法糖,并允许您创建由其他属性的值组成的动态属性(称为"计算属性")。
--编辑2(针对编辑后的问题)
IMO从服务器返回的VO应该封装在一个模型中,并将该模型传递给视图。模型实现latestClassification
,而不是VO,这允许视图直接调用模型上的该方法。
一个简单的方法(对于您的实现来说可能很简单)是覆盖模型的parse
方法以返回合适的属性:
var modelFoo = Backbone.Model.extend({
parse: function ( json ) {
var i18n = ... ;
if (i18n.prefLang && _.isArray(json.label)) {
// json.label = "complex structure"
}
return json;
}
});
这样,只有您的模型担心如何格式化来自服务器的数据,而不添加另一层抽象。