• 做了这么多年前端,为什么你还是不会写业务代码?


      我在平时工作中也会负责一些代码审查的工作,做的多了,就发现了一个问题:大部分程序员习惯把所有的逻辑都写在vue文件里,所以这就导致一个问题,也就是你项目中的.vue文件的代码会巨多,相反,你的js/ts文件中的代码会没有那么多。如果是一个小型项目当然还好,如果是一个大型的项目,这可能就是维护人员的噩梦了,因为后续的维护成本是巨大的。至于为什么,相信你看完这篇博客就能懂我的意思了。

      今天有时间静下心可以写一篇博客,总结了一下日常审过的代码,我们就用这些代码当作案例,分析一下我们以后的coding生涯该如何去避免这些问题。

      首先我们想思考一个问题:.vue文件到底是用来干嘛用的?

      我们可以看一下vue官网是如何介绍这款框架的:

    image

      这个视图层也就vm,说白了就是我们写的页面。

      那么页面中应该负责干什么呢?无非就是用htmlcss代码搭建页面结构,然后页面中可以能有一些元素的事件,比如说按钮的点击事件,表单的查询,表格的请求数据的动作等等。

      所以综上所述我们可以总结一句话: .vue文件中包含一些页面结构以及页面元素动作的发起。

      那么,动作的发起者有了,动作的执行者是谁呢?

      我们看一些我们平常写的项目结构是啥样的,拿一个ts项目为例:

    image

      这个是src下的目录结构,应该是分的比较详细的,各个文件夹有不同的作用,这也符合单一职责。

      我们在回过头来看一些谁能当作动作的执行者呢?api只负责调用接口,他拿到数据后就抛出了,数据这时候已经流向别处了,assets只放静态资源,这两个显然不能。componentpublic_componentinstancemixinviews全是用来搭建页面的,mixin虽然是ts文件,但是他的定义也是可以分发Vue组件中的可复用功能,这些显然也是不行的。interfaceutils这些是抽象出来的东西,显然是不能放业务代码的。store现在已经不被大家看好了,因为vuex中的通信实在是太繁琐了,目前我们的项目只用来放一些字典数据用来缓存。还剩下routersmiddlewarestyle这三个显然不是干这个用的。最后就只剩下domain了。

      domain(也有叫service的)作为业务层,我们给他最初的定义就是写一些业务代码,按钮的点击事件,页面的滚动事件显然不是业务代码,这些可以负责调用业务代码,也就是我们上面说的动作的发起者。这时候业务层的作用就来了,就是充当动作的执行者。设想一下,我点击一个按钮去请求一个表格数据,那么按钮上肯定绑定一个click事件,点击完成之后就应该调接口了,后续的事情就可以交给业务层了,由业务层完成后续的逻辑,比如我拿数据需要调用api层先请求一下后台数据,拿到数据看一下有没有请求成功,数据是不是我们想要的格式,处理好时候就可以交给页面层渲染了。

      好,我看一下实际的代码表现是什么样的。

      先说一个简单需求,我们希望一个表格内展示一些用户数据,看一下一般的程序员是怎么做的:

    数据结构:

        // 表格数据
        let tableData = [
            {
                date: "2016-05-02",
                name: "王小虎",
                address: "上海市普陀区金沙江路 1518 弄",
                phone: "13033443344",
                love: [
                    '吃饭',
                    '睡觉',
                    '写bug'
                ]
            },
             {
                date: "2016-05-02",
                name: "王小虎",
                address: "上海市普陀区金沙江路 1518 弄",
                phone: "13033443344",
                love: [
                    '吃饭',
                    '睡觉',
                    '写bug'
                ]
            }
            ...
        ]
    
       // 表头数据
    
       let headData = [
            {
                label: "日期",
                prop: "date",
                 "180"
            },
            {
                label: "姓名",
                prop: "name",
                 "180"
            }
            ....
       ]
    
    

    .vue文件

    <template>
        <div class="preivew-page">
            <el-table :data="tableData" style=" 80%">
                <el-table-column v-for="(item, index) in headList" 
                            :key=index 
                            :prop="item.prop" 
                            :label="item.label" 
                            :width="item.width">
                    <template slot-scope="scope" >
                        <span v-if="item.prop=='job'">{{scope.row.jobInfo.job}}</span>
                        <span v-else-if="item.prop=='love'">{{scope.row.love.join('-')}}</span>
                        <span v-else>{{scope.row[item.prop]}}</span>
                    </template>
                </el-table-column>
            </el-table>
        </div>
    </template>
    <script lang='ts'>
    import { Vue, Component } from "vue-property-decorator";
    import { TableList } from "@/service/index";
    
    
    @Component
    export default class PreviewPage extends Vue {
        // 定义表格数据
        private tableData: any = [];
        // 定义表头数据
        private headList: any = [];
        async mounted () {
            // 请求表格数据
            let tableList = new TableList();
            this.tableData = await tableList.searchTableData()
            this.headList = await tableList.searchHeadData()
        }
    }
    </script>
    

    service业务层

    import { getTableData, getHeadData } from "@/api/index"
    
    export class TableList {
        public async searchTableData () {
            let data = await getTableData();
            return data;
        }
    
        public async searchHeadData () {
            let data = await getHeadData();
            return data;
        }
    }
    

    最终效果

    image

      我们可以看出这里的代码大部分写在了.vue文件中,里边包含了一些判断和数据处理,而且业务层文件基本没发挥它的作用,只是简单的调用了接口而已,我们知道一个类最基本的就是要构造他,才能体现它的多态,这里只是一个页面有表格业务操作,如果我们有很多个表格呢,是不是就会有很多个业务文件?

      如果我们按照我的说法,把业务代码不写在页面而是写在service中,看看是什么效果:

    .vue文件

    <template>
        <div class="preivew-page">
            <el-table :data="tableList.tableList" style=" 80%">
                <el-table-column v-for="(item, index) in tableList.headList" 
                            :key=index 
                            :prop="item.prop" 
                            :label="item.label" 
                            :width="item.width">
                </el-table-column>
            </el-table>
        </div>
    </template>
    <script lang='ts'>
    import { Vue, Component } from "vue-property-decorator";
    import { TableList } from "@/service/index_two";
    @Component
    export default class PreviewPage extends Vue {
        // 表格业务
        private tableList: any = new TableList();
        async mounted () {
            // 请求表格数据
            await this.tableList.searchTableData()
            await this.tableList.searchHeadData()
        }
    }
    </script>
    

    service业务层

    import { getTableData, getHeadData } from "@/api/index"
    
    export class TableList {
        // 表格数据
        public tableList: any;
        // 表头数据
        public headList: any;
    
        constructor (tableList =[],headList=[]) {
            this.tableList = tableList;
            this.headList = headList;
        }
    
        public async searchTableData () {
            let data: any = await getTableData();
            this.tableList = data.map(el => {
                Reflect.set(el, 'job', el.jobInfo.job);
                Reflect.set(el, 'love', el.love.join('-'));
                return el
            })
        }
    
        public async searchHeadData () {
            this.headList = await getHeadData();
        }
    }
    

      我们可以看出,业务类中定义了表格数据和表头,从后端获取到数据后直接挂在了业务类中,页面那些数据处理也在放在了业务类中,在页面中只需要把这个业务类存在vue实例中,在这个页面中只要是需要就可以拿到这个业务中的属性和方法。不仅如此,以后不管有多少个表格,只要是业务类似,都可以用这个业务类。是不是很方便?而且页面中的代码减少了很多。个人认为这种方式是比第一种要好很多的。

      不仅如此,有没有想过我们写的form表单也是只接写成业务的?把每个表单中的字段用类构造,这样页面中只接绑定类中的属性。因为类是引用类型,所以页面中表单有变化,业务类中是能响应的,这样表单的查询只接在类中取值就行了,而且表单构造相应变得简单了很多。我们看一下这个例子:

    service中的表单业务

    export class Form {
        public job: string;
        public love: string[];
        public address: string;
        constructor ( job='', love=[], address='' ) {
            this.job = job;
            this.love = love;
            this.address = address
        }
    }
    

    .vue页面

    <template>
        <div class="preivew-page">
            ...
            <el-drawer title="表单"
                        :visible.sync="drawer"
                        direction="rtl">
                <el-form ref="form" :model="form" :inline="true" label-width="100px" size="mini">
                    <el-form-item label="职位:">
                        <el-input v-model="form.job"></el-input>
                    </el-form-item>
                    <el-form-item label="爱好:">
                        <el-select v-model="form.love" multiple  placeholder="请选择爱好">
                        <el-option label="吃饭" value="吃饭"></el-option>
                        <el-option label="睡觉" value="睡觉"></el-option>
                        <el-option label="打豆豆" value="打豆豆"></el-option>
                        <el-option label="写bug" value="写bug"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="地址:">
                        <el-input v-model="form.address"></el-input>
                    </el-form-item>
                </el-form>
            </el-drawer>
        </div>
    </template>
    <script lang='ts'>
    import { Vue, Component } from "vue-property-decorator";
    import { Form } from "@/service/form"
    @Component
    export default class PreviewPage extends Vue {
        ...
        // 表单
        private form = new Form();
    
        openDialog () {
            this.drawer = true
        }
    
        // 点击表格中的编辑按钮
        handleClick ( row: any ) {
            let { job,love,address } = row
            this.form = new Form(job,love,address);
            this.openDialog()
        }
    }
    </script>
    

      新增表单需要初始化数据,就是类构造的过程,点击编辑按钮需要加载表格中的行数据,赋值到表单中,这时候需要拿到行数据再次构造一下业务类,是不是比赋值要方便很多,省去很多个等号?如果是个查询的表单,那么查询的业务,重置的业务都可以在类中取完成。

      我们再深入研究一下,这个页面中用到了侧滑组件,想想我们在一个项目中是不是有很多个侧滑,弹窗等等,那么每次用都要写一堆的打开关闭这些事件吗?其实这些简单的逻辑不是我们重点关注的,如果我把这些流程写成一个业务呢?看看下边的代码:

    .vue文件

    <template>
        <div class="preivew-page">
            <el-drawer title="表单"
                        :visible.sync="drawer.visible"
                        direction="rtl">
                ...
            </el-drawer>
    
            <el-button @click="drawer.open()">打开</el-button>
            <el-button @click="drawer.close()">关闭</el-button>
    
        </div>
    </template>
    <script lang='ts'>
    import { Vue, Component } from "vue-property-decorator";
    import { Form } from "@/service/form";
    import { Drawer } from "@/service/drawer"
    @Component
    export default class PreviewPage extends Vue {
        // 弹层控制
        private drawer = new Drawer()
        // 表单
        private form = new Form();
        handleClick ( row: any ) {
            let {job,love,address} = row
            this.form = new Form(job,love,address);
            this.drawer.open()
        }
    }
    </script>
    

    service侧滑业务

    export class Drawer {
        public visible: boolean;
    
        constructor ( visible = false ) {
            this.visible = visible;
        }
    
        open () {
            this.visible = true;
        }
    
        close () {
            this.visible = false;
        }
    }
    

      这样每个侧滑组件我们都可以用这个业务了;甚至像弹窗这种类似的组件也能用这个业务。

      其实看到这里你应该就明白了,我们应该如何去写业务代码了。

      我们作为程序员,从刚入行开始写的就是业务代码,做的多了,应该学会自己把自己写的代码变得更加漂亮了。老满足于当下,不如搞点事情,写一些优雅的代码。

      业务代码其实是最好写的代码,只有业务代码写的多了,我们才能明白如何将业务下沉为公共服务,公共服务再下沉为基础服务,等到这时候,就不是写业务代码这么简单了,他更考验我们的抽象能力。

      本博客耗时三天,就是希望大家不要老写一些面条代码,面条代码是容易理解,但是那对我们的能力和技术一点不会有提升,只有善于封装,善于优化才能写出更加漂亮的代码。

    关注我的个人博客: 地址

    我的开源项目: 地址

    一天一句毒鸡汤小程序

    image

  • 相关阅读:
    有道翻译js解密(1)
    Python面试题之Python正则表达式re模块
    go语言从例子开始之Example4.常量
    go语言从例子开始之Example3.变量
    go语言从例子开始之Example2.类型
    go语言从例子开始之Example1.helloworld
    python模块打补丁
    gevent协程之猴子补丁带来的坑
    charles抓包小程序
    httptesting HTTP(s)接口自动化测试框架
  • 原文地址:https://www.cnblogs.com/qisi007/p/14087124.html
Copyright © 2020-2023  润新知