Vue2屎山代碼大盤點
前言
相比其他的框架來說,Vue中更容易產(chǎn)出屎山代碼;因為Vue中的options就是一個大對象,導(dǎo)致js本身的很多檢測都失效了,比如一個函數(shù)沒有用到的話會“變灰”,template中代碼提示比較少,較多的mixins等等;遇到屎山代碼,大多數(shù)人第一反應(yīng)就是這誰寫的代碼這么差,其實大多數(shù)公司大多數(shù)人至少曾經(jīng)都寫過一些屎山代碼,有屎山代碼很正常,問題在于怎么快速梳理出業(yè)務(wù)邏輯,防止在迭代新需求時引發(fā)bug,在富有余力的情況下可以進(jìn)行局部重構(gòu),漸進(jìn)式優(yōu)化屎山代碼;
今天重點就看一看Vue2中的那些屎山;
通用屎山
一號屎山--目錄雜亂
危害程度:??
file復(fù)制代碼src/ ├── App.vue ├── api ├── components ├── constants ├── main.js ├── pages ├── router ├── services ├── utils │ ? └── hash.js └── views
看一下上面的目錄,views和pages是類似的含義,都是指的路由對應(yīng)的頁面,而api和services也是類似的都是存放后端接口的封裝,同時存在這幾種文件夾說明項目初期沒有規(guī)范,每個人按照自己的規(guī)范去開發(fā),導(dǎo)致有的人頁面寫在views里面,有的寫在pages里面,建議這幾個相似含義的目錄只保留一個;
一號屎山
的危害在于讓后續(xù)接手的人要頻繁切換文件夾去看不同頁面的邏輯,并且不知道后續(xù)自己應(yīng)該在哪個文件夾開發(fā)自己的頁面,導(dǎo)致惡性循環(huán);
二號屎山--奇葩命名法
危害程度:??????????
奇葩命名法有以下幾種情況:
全拼音命名法
”畢竟都是中國人嘛,全拼音命名大家應(yīng)該都看得懂吧“,舉個例子:
dazhe.vue
。但是同一個拼音可以翻譯出不同的意思出來,他們之間是一對多的關(guān)系,因此不適合作為組件名;當(dāng)然,全拼音命名還算是手下留情的,有的時候全拼音命名可能會很長,那就直接取首字母吧!拼音首字母命名法
于是
dazhe.vue
變成了dz.vue
,這個時候就成了猜謎語,有一首歌詞寫得好:女孩的心思男孩你別猜別猜別猜你猜來猜去也猜不明白
,到了這里就是代碼的心思你別猜
,直接放棄吧!中西合璧命名法
有些同學(xué)覺得光中文那不太高大上啊,要把英語也加進(jìn)來才能顯示自己的水平,所以這樣命名:
dzList.vue
,照樣還是讓人看不懂英文首字母命名法
我想這種方式命名的同學(xué)應(yīng)該不多吧,畢竟已經(jīng)拿起翻譯工具翻譯了,直接cv就可以了,為什么還要摘出首字母來呢?
上面舉了文件名作為例子,其實命名規(guī)范充斥在所有程序員的每一項工作中,比如:變量命名、函數(shù)命名、類命名、接口命名,以我之見,嚴(yán)格遵循命名規(guī)范是編程的第一步,必須使用翻譯的英文來命名,英文就是一個字典,至少大部分的英文通過翻譯之后還是能夠準(zhǔn)確地知曉其含義的,這里面錯誤的概率遠(yuǎn)遠(yuǎn)低于以上幾種方式;
三號屎山--組件不拆分
危害程度:??????????
Vue將template、script、style組合在一個.vue文件中,這天然就會使得每一個.vue文件的行數(shù)會非常多,難以維護(hù),Vue2中一個最明顯的屎山就是幾千行、甚至上萬行的代碼
,用專業(yè)的術(shù)語來講就是不符合單一職責(zé)原則
,一個組件應(yīng)該只干一件事情,一個函數(shù)應(yīng)該只處理一個邏輯,剩下的邏輯交給其他函數(shù)或者組件來做;時刻牢記“SOLID”原則是遠(yuǎn)離屎山的第一心法;
前面通用屎山已經(jīng)堆積到一定高度,接下來再加大馬力,看看template屎山、script屎山和style屎山。
template屎山
四號屎山--復(fù)雜的表達(dá)式
危害程度:??????
js復(fù)制代碼 <div ?? ?class="files" ?? ?:class="{ disabled: !isAllowRead && hasNotPassed && aaa && (bbb || ccc) }" ?? ?@click="toDetail()" ??> ??<a/> ??<b v-if="!isAllowRead && hasNotPassed && aaa && (bbb || ccc)"/> ?</div>
看這一段代碼,為了判斷一個禁用狀態(tài),使用了大量的運算符,導(dǎo)致邏輯不清晰,并且遇到相似的邏輯在下面b組件上不得不ctrl cv,妥妥地變成了cv工程師,這里正確的做法是應(yīng)該放到計算屬性里面去進(jìn)行判斷,并且根據(jù)后面所使用到的邏輯進(jìn)行計算屬性的拆分:
diff復(fù)制代碼 ? ? <div ? ? ? ? ?:class="{ disabled: isFileDisabled }" ?? ? ? ?@click="toDetail()" ?? ? ?> ?? ? ?<a/> ?? ? ?<b v-if="isFileDisabled"/> ?? ? </div> ?? ? <script> ?? ? ? ? export default { ?? ? ? ? ? ? // 此處省略... ?? ? ? ? ? ? computed: { + ? ? ? ? ? ? ?isFileDisabled(){ + ? ? ? ? ? ? ? ? return !isAllowRead && hasNotPassed && aaa && (bbb || ccc) + ? ? ? ? ? ? ?} ?? ? ? ? ? ? } ?? ? ? ? } ?? ? </script>
當(dāng)然isFileDisabled這個計算屬性也可以拆分成多個,這個主要看后續(xù)的復(fù)用情況;所以二號屎山的優(yōu)化方案就是利用計算屬性或者拆分計算屬性
五號屎山--大量重復(fù)節(jié)點
危害程度:??????
html復(fù)制代碼<template> ??<div> ?? ?<span>姓名:{{ name }}</span> ?? ?<span>年齡:{{ age }}</span> ?? ?<span>性別:{{ gender }}</span> ?? ?<span>身高:{{ height }}</span> ?? ?<span>體重:{{ weight }}</span> ?? ?<span>愛好{{ habit }}</span> ??</div> </template>
優(yōu)化后的代碼:
diff復(fù)制代碼 ?<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" } + ? ? ?] ?? ?}; ??},
可能有些同學(xué)認(rèn)為這個不算是屎山代碼,但是當(dāng)這個span變得復(fù)雜起來之后甚至這個span里面包含了幾十行代碼的時候,就會發(fā)現(xiàn)這里面的重復(fù)元素太多了,進(jìn)而無法維護(hù);
script屎山
六號屎山--if else switch
危害程度:????
js復(fù)制代碼if(!values.username){ ?? ?this.$message.error("用戶名不能為空") } else if(!values.password){ ?? ?this.$message.error("密碼不能為空") } else if(!values.phoneNumber){ ?? ?this.$message.error("手機(jī)號不能為空") } else { ?? ?this.submit(); }
可能有人會說,上面的代碼語義明確,寫得還不夠好嗎?但是如果需要增加更多的校驗條件時,開發(fā)者不得不侵入到具體方法去修改代碼,使用策略模式優(yōu)化之后能夠讓校驗條件與具體判斷邏輯解耦,當(dāng)需要增加校驗條件時直接修改數(shù)組即可:
js復(fù)制代碼const validators = [ ??{ message: "用戶名不能為空", required: true, key: "username" }, ??{ message: "密碼不能為空", required: true, key: "password" }, ??{ message: "手機(jī)號不能為空", 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; ?? ? ?} ? ? ? ?// ... 調(diào)用接口 ?? ?} ??} };
七號屎山--后端參數(shù)處理
危害程度:??????
js復(fù)制代碼 ? ?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; ?? ? ?// ... 此處省略一萬行代碼 ?? ?}
看到這樣的代碼內(nèi)心是崩潰的,明顯只有幾個字段需要處理一下卻把所有字段都賦值了一遍,可以這樣簡化:
js復(fù)制代碼 ? ?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(); ?? ? ?// ... 此處省掉一萬行代碼 ?? ?}
八號屎山--硬編碼
危害程度:????????
vue復(fù)制代碼 ?computed: { ?? ?isGood() { ?? ? ?return this.type === 1; ?? ?}, ?? ?isBad() { ?? ? ?return this.type === 0; ?? ?} ??}
看上面的例子,這種硬編碼基本隨處可見,作者在寫這段代碼的時候肯定是覺得這個type只會在這里用到,沒有必要單獨定義一個常量來管理,后面接收的同學(xué)來了他也不會去關(guān)注之前的邏輯,他只要用到了type又會去重新判斷一下是good還是bad,就這樣最后代碼中充斥著0,1,2,3這樣的數(shù)字,后來新人接到一個需求并且涉及到這些數(shù)字背后的含義這個時候就不得不去一個一個地詢問原作者了,好的做法就是寫成常量配置文件,單獨寫一個文件config.js,然后組件去引用這個常量:
js復(fù)制代碼// 貨物的品質(zhì)枚舉值 export const GOODS_TYPE = { ??good: 1, // 質(zhì)量好 ??bad: 0 ? // 質(zhì)量差 };
九號屎山--Mixins屎山
危害程度:??????
我不生產(chǎn)代碼,我只是Mixins的搬運工:
js復(fù)制代碼// 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提供了一些數(shù)據(jù),最后統(tǒng)一在c.vue中使用,這樣的話容易造成變量覆蓋以及來路不明等問題,如果必須使用vue2的話這種情況是避免不了的,只能盡量減少組件對mixins中data的耦合度,但是最近看到一篇文章打開了新的思路,有興趣的可以讀一讀:我可能發(fā)現(xiàn)了Vue Mixin的正確用法——動態(tài)Mixin
十號屎山--無用的methods不刪除
危害程度:????
在Vue2中Eslint檢測不了methods是否被引用,所以這一塊不會報錯,當(dāng)開發(fā)者修改功能時可能有些methods不會再用到了但是又不主動去刪除,這個時候就會造成無用代碼的堆積;針對這種情況,我也正在考慮是否可以寫一個Eslint插件去檢測這些無用的methods;
style屎山
十一號屎山--類名無規(guī)范
危害程度:??
將id,駝峰、橫線、下劃線結(jié)合使用:
css復(fù)制代碼#id{} #App{} .AppBuy{} .app-buy{} .app_buy{} .App_Buy{}
好的css是有一定的規(guī)范的,禁止使用id選擇器、!important;類名用橫線分割,或者參考BEM規(guī)范
十二號屎山--樣式大量重復(fù)
危害程度:??
css復(fù)制代碼.a{ ?? ?display:flex; ?? ?align-items:center; ?? ?justify-content:center; } .b{ ?? ? display:flex; ?? ? align-items:center; ?? ? justify-content:center; ?? ? font-size:16px; ?? ? color:red; }
css樣式大量重復(fù)導(dǎo)致css文件體積劇增,特別是在樣式基本固定的后臺系統(tǒng)中,寫樣式其實是一個痛苦的事情,因此最好是做到原子化公共樣式與業(yè)務(wù)具體樣式的分離
最后一個屎山--不寫注釋
危害程度:?????????? 寫個注釋是舉手之勞,花不了多少時間,而且前面所有的屎山堆起來,如果有注釋的話還是可以快速理解其含義的,但是如果再加上不寫注釋,那就是天坑了,誰也救不了這個屎山;
羅馬的道路不是一日鋪成的,屎山的代碼也不是一天寫成的,而是在每個開發(fā)者無所謂的心態(tài)下堆成的,如果平時多注意注意至少也能保證自己寫的代碼”留有余香“。
建議讀完本文之后再讀一讀參考文章,最后是嚴(yán)格地執(zhí)行!如果以時間不夠為借口而不執(zhí)行那么看再多的文章也沒有用!
堆屎山?jīng)]有終點,持續(xù)更新中......
更新于2023.06.23
十四號屎山--組件不寫name
危害程度:??
js復(fù)制代碼// 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,然后導(dǎo)入的時候隨便設(shè)置一個其他的名字,在父組件中使用,這樣在vue-devtools中查看的話,組件名為MyComponent,如果只有這樣一個組件問題不大;想一下所有組件都不設(shè)置name,然后從根組件開始每一層組件都有一個叫Button的組件,一個新人接手這個項目了,他用devtool打開一看全是Button組件,看起來貌似是一樣但是其實不一樣,而且要搞清楚到底對應(yīng)的代碼在哪里還很費時間;如果定義了name,那么即使改變注冊的key,組件名也是固定的,另外推薦組件名與文件名一致,這樣大大地降低了組件搜索成本;