前言
最近一直在忙公司的业务开发活动,从中经历了和产品、Web 后端、底层、测试同学的爱恨情仇,最后想把自己的一点经验总结一下,顺便拿最坑的那个部分做个例子。
Vue 的 MVVM 和 MVC
我们常说 Vue 是 MVVM 的库,又说它不是框架,只是前端的 View 层,MVVM 不就包括 View 吗?为什么又说 Vue 或 React 只是 View 层面上的库呢?
先祭出图:
vue 业务套路.png
我们常说的 MVVM,实际上是指:
- 视图层,也就是 HTML+CSS 组成的页面
- 模型层,通常指前端组件需要的数据
- 视图模型层,它会在视图和模型之间进行通信。通俗点讲,就是通过指令,将视图中触发的事件传达给模型层,将模型层的数据编译成视图。
实际开发中后端的数据和前端所需的数据结构往往不匹配,所以这时我们在前端还是需要一个 MVC:
- 视图层,这块是纯前端内容
- 模型层,这块是纯后端内容,只通过接口暴露数据,我们前端通常通过 ajax 获取这些数据
- 控制器层,这块是视图和数据进行适配的地方,如果后端数据不符合前端需求,应该由它来转化。
如果业务简单,或者故意不分层,那么前端就会把 MVC 的代码都写在一坨,而我们分离它们,自然是为了以后应对后端和产品的不定时更改。
一个小例子
有一天,我接到一个需求,做一个表单提交页,这没什么,但它有十颗树。
但最悲催的是,后端给了我一个这样的数据结构:
{
data: {
api: [{
prop: [{
name: 'length',
state: 1
},
{
name: 'prototype',
state: 1
}
],
static_method: [{
name: 'from',
state: 0
},
{
name: 'isArray',
state: 0
},
{
name: 'of',
state: 1
}
],
proto_method: [{
name: 'toString',
state: 1
},
{
name: 'toLocalString',
state: 1
},
{
name: 'pop',
state: 1
},
{
name: 'push',
state: 1
},
{
name: 'shift',
state: 1
},
{
name: 'unshift',
state: 1
},
{
name: 'find',
state: 1
},
{
name: 'findIndex',
state: 1
},
{
name: 'includes',
state: 1
},
{
name: 'indexOf',
state: 1
},
{
name: 'lastIndexOf',
state: 1
},
{
name: 'join',
state: 1
},
{
name: 'slice',
state: 1
},
{
name: 'concat',
state: 1
},
{
name: 'splice',
state: 1
},
{
name: 'copyWithin',
state: 1
},
{
name: 'keys',
state: 1
},
{
name: 'enenties',
state: 1
},
{
name: 'values',
state: 1
},
{
name: 'every',
state: 1
},
{
name: 'some',
state: 1
},
{
name: 'sort',
state: 1
},
{
name: 'reverse',
state: 1
},
{
name: 'forEach',
state: 1
},
{
name: 'map',
state: 1
},
{
name: 'filter',
state: 1
},
{
name: 'reduce',
state: 1
},
{
name: 'reduceRight',
state: 1
}
],
type: 'Array'
},
{
prop: [{
name: 'prototype',
state: 1
}],
static_method: [{
name: 'assign',
state: 1
},
{
name: 'create',
state: 1
},
{
name: 'defineProperties',
state: 1
},
{
name: 'defineProperty',
state: 1
},
{
name: 'getOwnPropertyDescriptor',
state: 1
},
{
name: 'getOwnPropertyDescriptors',
state: 1
},
{
name: 'getOwnPropertyNames',
state: 1
},
{
name: 'getOwnPropertySymbols',
state: 1
},
{
name: 'getPrototypeOf',
state: 1
},
{
name: 'is',
state: 1
},
{
name: 'isExtensible',
state: 1
},
{
name: 'isFrozen',
state: 1
},
{
name: 'isSealed',
state: 1
},
{
name: 'keys',
state: 1
},
{
name: 'preventExtensions',
state: 1
},
{
name: 'seal',
state: 1
},
{
name: 'freeze',
state: 1
},
{
name: 'values',
state: 1
},
{
name: 'setPrototypeOf',
state: 1
}
],
proto_method: [{
name: 'hasOwnProperty',
state: 1
},
{
name: 'isPrototypeOf',
state: 1
},
{
name: 'propertyIsEnumerable',
state: 1
},
{
name: 'toString',
state: 1
},
{
name: 'valueOf',
state: 1
}
],
type: 'Object'
},
{
prop: [{
name: 'prototype',
state: 1
},
{
name: 'length',
state: 1
}
],
static_method: [{
name: 'fromCharCode',
state: 1
},
{
name: 'fromCodePoint',
state: 1
}
],
proto_method: [{
name: 'charAt',
state: 1
},
{
name: 'indexOf',
state: 1
}
],
type: 'String'
},
{
prop: [{
name: 'prototype',
state: 1
}],
static_method: [{
name: 'UTC',
state: 1
},
{
name: 'now',
state: 1
}
],
proto_method: [{
name: 'getDate',
state: 1
},
{
name: 'getTime',
state: 1
}
],
type: 'Date'
}
],
api_type: [{
name: 'Array',
state: 1
},
{
name: 'Object',
state: 1
},
{
name: 'String',
state: 1
},
{
name: 'Date',
state: 1
}
]
}
}
我知道你肯定会没耐心地滋溜的滑下来,让我来解释一下这个数组:
每个元素的type键作为父节点,其他一个键共用一棵树。比如 prop 对应属性树、proto_method 对应原型方法树、static_method 对应静态方法树。此数组有四个元素,故四个元素共用三棵树,父节点由 type 键决定。
这个数据结构是肯定有缺陷的,比如:
- 每个元素是如何识别的?
- 以后树超过两层该怎么办?
按理来说,后端应该给每个元素赋予一个 id 或 index 来区分元素的不同,以便用户在前端选择某个具体的节点时知道那个节点在树中的位置。但是后端表示你爱用不用,于是我只能这样解决这两个问题了:
- 将元素的 name 属性当做 id,如果是子元素,那就是 type-name 拼接的值当 id
- 以后再出现树必须是扁平结构,类似:
{
name:'toString',
id:1,
pid:0,
state:1
}
为了减少读者老爷的头晕目眩感,我在例子中只放了三棵树,最后实现的效果如下图,在线演示地址
面对这种恶心的数据结构,如果前端写成一坨,非常有可能诞生一个一千行以上的 vue 文件,所以我们分层,是对未来的自己好,也是为以后接手的人好。
我们分层后,vue 的 js 部分(MVVM)只需要写成:
new Vue({
el: '#app',
data: {
treeData: [
{ 'title': '属性', data: [], checkedKeys: [] },
{ 'title': '原型方法', data: [], checkedKeys: [] },
{ 'title': '静态方法', data: [], checkedKeys: [] }
],
apiData: new DataProvider(sourceData.data)
},
methods: {
initTreeData() {
Object.assign(this.treeData[0], this.apiData.getProp())
Object.assign(this.treeData[1], this.apiData.getProtoMethod())
Object.assign(this.treeData[2], this.apiData.getStaticMethod())
}
},
mounted() {
this.initTreeData()
}
})
而 MVC 中的 C 里的逻辑,都封装到 DataProvider 里去了。业务部分只需要消费数据就行了。
DataProvider 除了 get* 的方法外,还应该要有 set* 的方法,用于将前端数据转成后端可用的数据结构(对,你没看错,丧心病狂的后端还要我在用户选完树后按那个恶心的结构传回去)。不过分层后一切都好做很多了,就算以后这块的数据结构又改,vue 文件里几乎是不需要改动的。
而 Vue 官方也是推荐我们将 MVC 的 V 层分离出来的,只不过是通过 Vuex 中实现:
vuex
结语
业务开发如搬砖,很多人嫌弃的不要的不要的,但是公司一般就是靠业务赚钱的,不能老让你研究公司感觉没什么卵用的赚不了钱的技术。
无论我们是开发业务项目还是开发技术产品项目,都应该静下心来,仔细琢磨每一处可以设计、构建的部分,荣辱不惊。
代码处处是学问。
网友评论