我在平时工作中也会负责一些代码审查的工作,做的多了,就发现了一个问题:大部分程序员习惯把所有的逻辑都写在vue文件里,所以这就导致一个问题,也就是你项目中的.vue
文件的代码会巨多,相反,你的js/ts
文件中的代码会没有那么多。如果是一个小型项目当然还好,如果是一个大型的项目,这可能就是维护人员的噩梦了,因为后续的维护成本是巨大的。至于为什么,相信你看完这篇博客就能懂我的意思了。
今天有时间静下心可以写一篇博客,总结了一下日常审过的代码,我们就用这些代码当作案例,分析一下我们以后的coding
生涯该如何去避免这些问题。
首先我们想思考一个问题:.vue
文件到底是用来干嘛用的?
我们可以看一下vue官网
是如何介绍这款框架的:
这个视图层也就vm
,说白了就是我们写的页面。
那么页面中应该负责干什么呢?无非就是用html
和css
代码搭建页面结构,然后页面中可以能有一些元素的事件,比如说按钮的点击事件,表单的查询,表格的请求数据的动作等等。
所以综上所述我们可以总结一句话: .vue
文件中包含一些页面结构以及页面元素动作的发起。
那么,动作的发起者有了,动作的执行者是谁呢?
我们看一些我们平常写的项目结构是啥样的,拿一个ts项目为例:
这个是src
下的目录结构,应该是分的比较详细的,各个文件夹有不同的作用,这也符合单一职责。
我们在回过头来看一些谁能当作动作的执行者呢?api
只负责调用接口,他拿到数据后就抛出了,数据这时候已经流向别处了,assets
只放静态资源,这两个显然不能。component
,public_component
,instance
,mixin
和views
全是用来搭建页面的,mixin
虽然是ts
文件,但是他的定义也是可以分发Vue组件中的可复用功能
,这些显然也是不行的。interface
和utils
这些是抽象出来的东西,显然是不能放业务代码的。store
现在已经不被大家看好了,因为vuex
中的通信实在是太繁琐了,目前我们的项目只用来放一些字典数据用来缓存。还剩下routers
,middleware
,style
这三个显然不是干这个用的。最后就只剩下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;
}
}
最终效果
我们可以看出这里的代码大部分写在了.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;
}
}
这样每个侧滑组件我们都可以用这个业务了;甚至像弹窗这种类似的组件也能用这个业务。
其实看到这里你应该就明白了,我们应该如何去写业务代码了。
我们作为程序员,从刚入行开始写的就是业务代码,做的多了,应该学会自己把自己写的代码变得更加漂亮了。老满足于当下,不如搞点事情,写一些优雅的代码。
业务代码其实是最好写的代码,只有业务代码写的多了,我们才能明白如何将业务下沉为公共服务,公共服务再下沉为基础服务,等到这时候,就不是写业务代码这么简单了,他更考验我们的抽象能力。
本博客耗时三天,就是希望大家不要老写一些面条代码,面条代码是容易理解,但是那对我们的能力和技术一点不会有提升,只有善于封装,善于优化才能写出更加漂亮的代码。