使用 Vue 开发业务的套路(模式)

前言

最近一直在忙公司的业务开发活动,从中经历了和产品、Web 后端、底层、测试同学的爱恨情仇,最后想把自己的一点经验总结一下,顺便拿最坑的那个部分做个例子。

Vue 的 MVVM 和 MVC

我们常说 Vue 是 MVVM 的库,又说它不是框架,只是前端的 View 层,MVVM 不就包括 View 吗?为什么又说 Vue 或 React 只是 View 层面上的库呢?

先祭出图:


vue 业务套路.png

我们常说的 MVVM,实际上是指:

  1. 视图层,也就是 HTML+CSS 组成的页面
  2. 模型层,通常指前端组件需要的数据
  3. 视图模型层,它会在视图和模型之间进行通信。通俗点讲,就是通过指令,将视图中触发的事件传达给模型层,将模型层的数据编译成视图。

实际开发中后端的数据和前端所需的数据结构往往不匹配,所以这时我们在前端还是需要一个 MVC:

  1. 视图层,这块是纯前端内容
  2. 模型层,这块是纯后端内容,只通过接口暴露数据,我们前端通常通过 ajax 获取这些数据
  3. 控制器层,这块是视图和数据进行适配的地方,如果后端数据不符合前端需求,应该由它来转化。

如果业务简单,或者故意不分层,那么前端就会把 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 键决定。

这个数据结构是肯定有缺陷的,比如:

  1. 每个元素是如何识别的?
  2. 以后树超过两层该怎么办?

按理来说,后端应该给每个元素赋予一个 id 或 index 来区分元素的不同,以便用户在前端选择某个具体的节点时知道那个节点在树中的位置。但是后端表示你爱用不用,于是我只能这样解决这两个问题了:

  1. 将元素的 name 属性当做 id,如果是子元素,那就是 type-name 拼接的值当 id
  2. 以后再出现树必须是扁平结构,类似:
{
  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

结语

业务开发如搬砖,很多人嫌弃的不要的不要的,但是公司一般就是靠业务赚钱的,不能老让你研究公司感觉没什么卵用的赚不了钱的技术。

无论我们是开发业务项目还是开发技术产品项目,都应该静下心来,仔细琢磨每一处可以设计、构建的部分,荣辱不惊。

代码处处是学问。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容