美文网首页
使用 Vue 开发业务的套路(模式)

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

作者: 云峰yf | 来源:发表于2018-03-22 21:26 被阅读0次

    前言

    最近一直在忙公司的业务开发活动,从中经历了和产品、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

    结语

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

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

    代码处处是学问。

    相关文章

      网友评论

          本文标题:使用 Vue 开发业务的套路(模式)

          本文链接:https://www.haomeiwen.com/subject/dsszqftx.html