相比其他的框架来说,Vue中更容易产出屎山代码;因为Vue中的options就是一个大对象,导致js本身的很多检测都失效了,比如一个函数没有用到的话会“变灰”,template中代码提示比较少,较多的mixins等等;遇到屎山代码,大多数人第一反应就是这谁写的代码这么差,其实大多数公司大多数人至少曾经都写过一些屎山代码,有屎山代码很正常,问题在于怎么快速梳理出业务逻辑,防止在迭代新需求时引发bug,在富有余力的情况下可以进行局部重构,渐进式优化屎山代码;
今天重点就看一看Vue2中的那些屎山;
危害程度:⭐️
src/
├── App.vue
├── api
├── components
├── constants
├── main.js
├── pages
├── router
├── services
├── utils
│ └── hash.js
└── views
看一下上面的目录,views和pages是类似的含义,都是指的路由对应的页面,而api和services也是类似的都是存放后端接口的封装,同时存在这几种文件夹说明项目初期没有规范,每个人按照自己的规范去开发,导致有的人页面写在views里面,有的写在pages里面,建议这几个相似含义的目录只保留一个;
一号屎山
的危害在于让后续接手的人要频繁切换文件夹去看不同页面的逻辑,并且不知道后续自己应该在哪个文件夹开发自己的页面,导致恶性循环;
危害程度:⭐️⭐️⭐️⭐️⭐️
奇葩命名法有以下几种情况:
”毕竟都是中国人嘛,全拼音命名大家应该都看得懂吧“,举个例子:dazhe.vue
。但是同一个拼音可以翻译出不同的意思出来,他们之间是一对多的关系,因此不适合作为组件名;当然,全拼音命名还算是手下留情的,有的时候全拼音命名可能会很长,那就直接取首字母吧!
拼音首字母命名法
于是dazhe.vue
变成了dz.vue
,这个时候就成了猜谜语,有一首歌词写得好:女孩的心思男孩你别猜别猜别猜你猜来猜去也猜不明白,到了这里就是代码的心思你别猜,直接放弃吧!
中西合璧命名法
有些同学觉得光中文那不太高大上啊,要把英语也加进来才能显示自己的水平,所以这样命名:dzList.vue
,照样还是让人看不懂
英文首字母命名法
我想这种方式命名的同学应该不多吧,毕竟已经拿起翻译工具翻译了,直接cv就可以了,为什么还要摘出首字母来呢?
上面举了文件名作为例子,其实命名规范充斥在所有程序员的每一项工作中,比如:变量命名、函数命名、类命名、接口命名,以我之见,严格遵循命名规范是编程的第一步,必须使用翻译的英文来命名,英文就是一个字典,至少大部分的英文通过翻译之后还是能够准确地知晓其含义的,这里面错误的概率远远低于以上几种方式;
危害程度:⭐️⭐️⭐️⭐️⭐️
Vue将template、script、style组合在一个.vue文件中,这天然就会使得每一个.vue文件的行数会非常多,难以维护,Vue2中一个最明显的屎山就是几千行、甚至上万行的代码
,用专业的术语来讲就是不符合单一职责原则,一个组件应该只干一件事情,一个函数应该只处理一个逻辑,剩下的逻辑交给其他函数或者组件来做;时刻牢记“SOLID”原则是远离屎山的第一心法;
前面通用屎山已经堆积到一定高度,接下来再加大马力,看看template屎山、script屎山和style屎山。
危害程度:⭐️⭐️⭐️
<div
class="files"
:class="{ disabled: !isAllowRead && hasNotPassed && aaa && (bbb || ccc) }"
@click="toDetail()"
>
<a/>
<b v-if="!isAllowRead && hasNotPassed && aaa && (bbb || ccc)"/>
</div>
看这一段代码,为了判断一个禁用状态,使用了大量的运算符,导致逻辑不清晰,并且遇到相似的逻辑在下面b组件上不得不ctrl cv,妥妥地变成了cv工程师,这里正确的做法是应该放到计算属性里面去进行判断,并且根据后面所使用到的逻辑进行计算属性的拆分:
diff复制代码
<div
class="files"
:class="{ disabled: isFileDisabled }"
@click="toDetail()"
>
<a/>
<b v-if="isFileDisabled"/>
</div>
<script>
export default {
// 此处省略...
computed: {
+ isFileDisabled(){
+ return !isAllowRead && hasNotPassed && aaa && (bbb || ccc)
+ }
}
}
</script>
当然isFileDisabled这个计算属性也可以拆分成多个,这个主要看后续的复用情况;所以二号屎山的优化方案就是利用计算属性或者拆分计算属性
危害程度:⭐️⭐️⭐️
<template>
<div>
<span>姓名:{{ name }}</span>
<span>年龄:{{ age }}</span>
<span>性别:{{ gender }}</span>
<span>身高:{{ height }}</span>
<span>体重:{{ weight }}</span>
<span>爱好{{ habit }}</span>
</div>
</template>
优化后的代码:
<div>
+ <span v-for="item in textConfigs" :key="item.valueKey">{{
+ response[item.valueKey]
+ }}</span>
</div>
data() {
return {
+ textConfigs: [
+ { label: "性别", valueKey: "name" },
+ { label: "年龄", valueKey: "age" },
+ { label: "性别", valueKey: "gender" },
+ { label: "身高", valueKey: "height" },
+ { label: "体重", valueKey: "weight" },
+ { label: "爱好", valueKey: "habit" }
+ ]
};
},
可能有些同学认为这个不算是屎山代码,但是当这个span变得复杂起来之后甚至这个span里面包含了几十行代码的时候,就会发现这里面的重复元素太多了,进而无法维护;
危害程度:⭐️⭐️
if(!values.username){
this.$message.error("用户名不能为空")
} else if(!values.password){
this.$message.error("密码不能为空")
} else if(!values.phoneNumber){
this.$message.error("手机号不能为空")
} else {
this.submit();
}
可能有人会说,上面的代码语义明确,写得还不够好吗?但是如果需要增加更多的校验条件时,开发者不得不侵入到具体方法去修改代码,使用策略模式优化之后能够让校验条件与具体判断逻辑解耦,当需要增加校验条件时直接修改数组即可:
const validators = [
{ message: "用户名不能为空", required: true, key: "username" },
{ message: "密码不能为空", required: true, key: "password" },
{ message: "手机号不能为空", required: true, key: "phoneNumber" }
];
export default {
methods: {
validator(values) {
const result = validators.some(el => {
if (el.required && !values[el.key]) {
this.$message.error(el.message);
return true;
}
});
return result;
},
submit(values) {
if (this.validator(values)) {
return;
}
// ... 调用接口
}
}
};
危害程度:⭐️⭐️⭐️
handleParams() {
const params = {};
params.id = this.formItem.id;
params.startDate = this.formItem.startDate.format("YYYY-MM-DD");
params.endDate = this.formItem.endDate.format("YYYY-MM-DD");
params.price = this.formItem.price.toString();
params.type = this.formItem.type;
params.total = this.formItem.total;
params.name = this.formItem.name;
params.comment = this.formItem.comment;
// ... 此处省略一万行代码
}
看到这样的代码内心是崩溃的,明显只有几个字段需要处理一下却把所有字段都赋值了一遍,可以这样简化:
handleParams() {
const { startDate, endDate, price, ...params } = this.formItem;
params.startDate = startDate.format("YYYY-MM-DD");
params.endDate = endDate.format("YYYY-MM-DD");
params.price = price.toString();
// ... 此处省掉一万行代码
}
危害程度:⭐️⭐️⭐️⭐️
computed: {
isGood() {
return this.type === 1;
},
isBad() {
return this.type === 0;
}
}
看上面的例子,这种硬编码基本随处可见,作者在写这段代码的时候肯定是觉得这个type只会在这里用到,没有必要单独定义一个常量来管理,后面接收的同学来了他也不会去关注之前的逻辑,他只要用到了type又会去重新判断一下是good还是bad,就这样最后代码中充斥着0,1,2,3这样的数字,后来新人接到一个需求并且涉及到这些数字背后的含义这个时候就不得不去一个一个地询问原作者了,好的做法就是写成常量配置文件,单独写一个文件config.js,然后组件去引用这个常量:
js复制代码
// 货物的品质枚举值
export const GOODS_TYPE = {
good: 1, // 质量好
bad: 0 // 质量差
};
危害程度:⭐️⭐️⭐️
我不生产代码,我只是Mixins的搬运工:
// a.mixin.js
export default {
data() {
return {
username: "",
password: "",
age: 18
};
},
created() {
this.fetchUserInfo();
},
methods: {
fetchUserInfo() {}
}
};
// b.mixin.js
export default {
data(){
return {
height:'',
weight:''
}
},
created(){
this.fetchBodyFat();
},
methods:{
fetchBodyFat(){
}
}
}
// c.vue
const DEGREEMAP = {
doctor:'博士'
}
export default {
mixins:[a,b],
data(){
return {
degree:DEGREEMAP.doctor
}
},
created(){
this.log()
},
methods:{
log(){
if(this.age < 30 && this.height>180 && this.degree===DEGREEMAP.doctor){
alert("真牛!")
}
}
}
}
这里a、b提供了一些数据,最后统一在c.vue中使用,这样的话容易造成变量覆盖以及来路不明等问题,如果必须使用vue2的话这种情况是避免不了的,只能尽量减少组件对mixins中data的耦合度,但是最近看到一篇文章打开了新的思路,有兴趣的可以读一读:我可能发现了Vue Mixin的正确用法——动态Mixin
危害程度:⭐️⭐️
在Vue2中Eslint检测不了methods是否被引用,所以这一块不会报错,当开发者修改功能时可能有些methods不会再用到了但是又不主动去删除,这个时候就会造成无用代码的堆积;针对这种情况,我也正在考虑是否可以写一个Eslint插件去检测这些无用的methods;
危害程度:⭐️
将id,驼峰、横线、下划线结合使用:
#id{}
#App{}
.AppBuy{}
.app-buy{}
.app_buy{}
.App_Buy{}
好的css是有一定的规范的,禁止使用id选择器、!important;类名用横线分割,或者参考BEM规范
危害程度:⭐️
.a{
display:flex;
align-items:center;
justify-content:center;
}
.b{
display:flex;
align-items:center;
justify-content:center;
font-size:16px;
color:red;
}
css样式大量重复导致css文件体积剧增,特别是在样式基本固定的后台系统中,写样式其实是一个痛苦的事情,因此最好是做到原子化公共样式与业务具体样式的分离
罗马的道路不是一日铺成的,屎山的代码也不是一天写成的,而是在每个开发者无所谓的心态下堆成的,如果平时多注意注意至少也能保证自己写的代码”留有余香“。
建议读完本文之后再读一读参考文章,最后是严格地执行!如果以时间不够为借口而不执行那么看再多的文章也没有用!