0%

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

  我在平时工作中也会负责一些代码审查的工作,做的多了,就发现了一个问题:大部分程序员习惯把所有的逻辑都写在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",
            width: "180"
        },
        {
            label: "姓名",
            prop: "name",
            width: "180"
        }
        ....
   ]

.vue文件

<template>
    <div class="preivew-page">
        <el-table :data="tableData" style="width: 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="width: 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;
    }
}

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

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

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

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

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

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道