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