likes
comments
collection
share

Vue2屎山代码大盘点

作者站长头像
站长
· 阅读数 16

前言

相比其他的框架来说,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屎山。

template屎山

四号屎山--复杂的表达式

危害程度:⭐️⭐️⭐️

 <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工程师,这里正确的做法是应该放到计算属性里面去进行判断,并且根据后面所使用到的逻辑进行计算属性的拆分:

     <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里面包含了几十行代码的时候,就会发现这里面的重复元素太多了,进而无法维护;

script屎山

六号屎山--if else switch

危害程度:⭐️⭐️

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,然后组件去引用这个常量:

// 货物的品质枚举值
export const GOODS_TYPE = {
  good: 1, // 质量好
  bad: 0   // 质量差
};

九号屎山--Mixins屎山

危害程度:⭐️⭐️⭐️

我不生产代码,我只是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

十号屎山--无用代码不删除

危害程度:⭐️⭐️

大段注释不删除,没用的methods也不注释,本着多一事不如少一事的原则,因为Vue中父组件调用子组件方法比较常见,因此有些方法不使用了,自己也不去注释或者删除,害怕引发其他bug,或者干脆就不管,直接写一个其他名称的方法

无用的文件不删除,比方说开始定义了一个list.vue,后面这个文件重构了,直接增加了一个List.vue,目录中同时存在这两个文件,让人摸不着头脑,无形增加了项目复杂度,试想一下如果一个超大项目,一半的文件都是没有引用到的,那是多么的可怕!

style屎山

十一号屎山--类名无规范

危害程度:⭐️

将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文件体积剧增,特别是在样式基本固定的后台系统中,写样式其实是一个痛苦的事情,因此最好是做到原子化公共样式与业务具体样式的分离

最后一个屎山--不写注释

危害程度:⭐️⭐️⭐️⭐️⭐️

写个注释是举手之劳,花不了多少时间,而且前面所有的屎山堆起来,如果有注释的话还是可以快速理解其含义的,但是如果再加上不写注释,那就是天坑了,谁也救不了这个屎山;

罗马的道路不是一日铺成的,屎山的代码也不是一天写成的,而是在每个开发者无所谓的心态下堆成的,如果平时多注意注意至少也能保证自己写的代码”留有余香“。

建议读完本文之后再读一读参考文章,最后是严格地执行!如果以时间不够为借口而不执行那么看再多的文章也没有用!

堆屎山没有终点,持续更新中......


更新于2023.06.23

十四号屎山--组件不写name

危害程度:⭐️

// temp.vue
<template>
  <div>
    temp
  </div>
</template>
<script>
export default {};
</script>

// App.vue
<template>
    <div id="app">
        <my-component/>
    </div>
</template>
<script>
import MyComponent from "@/components/temp";

export default {
     components: {
        MyComponent
     },
};
</script>

temp组件不写name,然后导入的时候随便设置一个其他的名字,在父组件中使用,这样在vue-devtools中查看的话,组件名为MyComponent,如果只有这样一个组件问题不大;想一下所有组件都不设置name,然后从根组件开始每一层组件都有一个叫Button的组件,一个新人接手这个项目了,他用devtool打开一看全是Button组件,看起来貌似是一样但是其实不一样,而且要搞清楚到底对应的代码在哪里还很费时间;如果定义了name,那么即使改变注册的key,组件名也是固定的,另外推荐组件名与文件名一致,这样大大地降低了组件搜索成本;

参考文章