什么情况下适合在knockout项目中应用KOMapper
一.背景:在使用knockout搭建前端MVVM框架时,我们往往需要维护如下三个层次的数据结构或者模板
1.server side (Model Provider) :为了业务数据的存取,和数据一致,我们需要在服务端维护一系列可以对json进行增删改查的API,这里以MongoDB为例,通过API存取的对象是mongodb里的一个json文档.
eg:
{userName:'aa',email:'bb@xx.com'}
2.View Model:我们在js模块中定义一个viewModel对象,这个对象应该可以接收后端API返回的json的各个字段,并使用ko去定义需要的数据变化跟踪和数据关联和联动。
eg:
var vm = {user:{userName:ko.observable(),email:ko.observable()}};
var user = {js object from backen api};
vm.user.userName(user.userName);
vm.user.email(user.email);
3.html template(View):我们将vm中的字段通过data-bind绑定到页面,这样页面上的数据更新就和vm里的数据就可以双向同步了.
User Name:<input data-bind='value:userName' />
Email:<input data-bind='value:email' />
二.问题:我们找到了KOMapper这样一个工具,它能帮助自动根据api获取到的json去生成viewModel,我们在给viewModel添加业务需要的跟踪和联动,当然,komapper生成的viewModel能直接绑定到页面。但是,具体的使用情况和不适用komapper的对比如下:
1.json只有一层结构,根节点需要双向绑定
1.1 业务数据模型:
data = {
xxDetail:"",
yyDetail:"",
zzDetail:""
};
1.2 数据和vm的mapping
var vm = koMapper.fromJS(data);
1.3 vm和template的绑定
<input data-bind="value:xxDetail" />
<input data-bind="value:yyDetail" />
...
1.4 评价
KOMapper省去了,vm的预定义,省去了各个字段的赋值操作,减少了一层定义就减少了工作量和错误率,节省了时间。
2.json包含Array节点,Array的成员的根节点需要绑定
2.1 业务数据模型
data = {
content:[
offeringId:"000-0000-0000000",
offering:"xx",
comment:"yy"
]
}
2.2 数据和vm的mapping
var mapping = {
'content': {
$key: 'offeringId',
$itemOptions: {
'offeringId': 'copy',//不需要observable的属性
'offering': 'copy',
$type: KOLocationTarget,
//成员需要处理之后适应页面绑定,如该行增加提示信息(如:info,但该字段对业务来说数据模型不需要),增加对于改行数据的动态验证信息(如:isValidated);
}
}
}
var vm = koMapper .fromJS(data,mapping);
2.3 vm和template的绑定
<!—ko:foreach:content-->
<a data-bind="attr:{title:$data.info}"></a>
<span data-bind="text:offering"></span>
<span data-bind="if:!isValidated">
Please check…</span>
<!--/ko-->
2.4 评价
对于Array的绑定,我们需要定义如2.2所示的handler去告诉KOMapper如何去生成vm对象.
3 json层次较深而且包含根节点需要被绑定和中间节点需要绑定的情况
3.1 业务数据模型
data = {
coreTeam:{
ams:{
bidMgr:{//该节点需要obserable,
name:"",//该节点不需要obserable
email:""
}
}
}
}
3.2 数据和vm的mapping
针对上述结构中的bidMgr,这是我项目中遇到的一个情况,我们有一个people控件去处理{name:'',email''}这样的对象,其实{name:'',email''}这样的对象就作为一个数据单元存在,如果对其中的name或者email进行observable,这样只会增加ko的运算量和内存消耗,也会增加我们people控件中对observable 节点的unwrap等处理,这就增加了许多多余的或者还是有害的observable节点。所以如果使用KOMapper,我们需要跳过一些不必要呗observe的节点。 从而关心我们的业务和功能真正需要检测和双向绑定的对象。
类似的需求还有:data:{docs:[{name:'',title:'',url:''},{name:'',title:'',url:''}]},这样的数据中,其实只需要将docs绑定到页面上的table中.
var mapping= {
$default: 'skipSomeNode',//必须要写handler,因为在整个data下边,我们只需要bidMgr这个节点被observe,coreTeam,ams不需要observe。
};
koMapper.handlers.skipSomeNode = {
fromJS: function(node, options) {
var nodesNeedSkip = ['coreTeam', 'extendTeam', 'ams', 'apj', 'emea', 'primary', 'other'],
handleKOMap = function (node, nodesToSkip, options) {
var needSkip = false,
temp = {};
for (var child in node) {
if (nodesToSkip.indexOf(child)!== -1) {
needSkip = true;
}
}
if (needSkip) {
for (var child in node) {
temp[child] = koMapper.fromJS(node[child], options);
}
return temp;
} else {
for (var child in node) {
if(node[child] instanceof Array){
temp[child] = ko.observable(node[child]);//这个时候需要用 ko.observable(),因为我们只需要对bidMgr这个节点进行绑定。
}
return temp;
}
};
return handleKOMap(node,nodesNeedSkip,options);
}
3.3 绑定
<td style="text-align:left"><people
params="{people:$component.data['coreTeam']['ams']['bidMgr']}">//该组件需要展示user的name和email,需要根据email去站点拿其他用户信息。
</people></td>
3.4 评价
到这里,这样复杂程度的业务数据,KOMapper就很不实用,而且增加了handler设计的工作量和错误风险,而且业务数据根据需求变化时,需要重新设计更复杂的handler.
4. 其他情况
如果使用了koMapper,我们其实也有必要在前端template中预绑定一个数据模型和后端一样但数据都是默认值的viewModel,这样在ajax去异步取到后端数据之前,我们的页面才不会空。 总之即使使用了koMapper,前端也必须有一份结构一样的viewModel,因为当前我们后端得到的数据中可能不存在我们前端的业务所需的文档,所以如果最起先的数据产生是从前端发起的,预定义viewModel也是必须的。一旦后端有了数据,在后端数据取到并mapping完毕,产生的mapperViewModel再去替换前端默认的viewModel,这时候ko要去重新做一次绑定
三.总结
1.下面情况建议使用KOMapper:
1.1 数据从后端发起
1.2 业务数据模型层次较少
1.3 不存在大量的中间节点才需要被observable的对象
2.KOMapper的思想
根据后端取到的数据来生成viewModel,这样就只需要页面上的绑定和API提供的json结构对应。
3.不适用KOMapper,就和文章开头所说,后端提供Model,js 模块定义viewModel,template做绑定.