前端代碼規範
最近組內經常進行CodeReview,於是參考一些大廠規範以及一些開源的優秀源碼,整理了一些前端代碼規範,幫助我們後續可以寫出更好維護的代碼。有什麼不對的地方,歡迎大家指出,一起學習進步!
先來看下下面這張思維導圖:
下面會就幾個方面展開來説。
命名規範
駝峯式命名法介紹
- Pascal Case大駝峯式命名法:首字母大寫。eg:PersonInfo
- Camel Case小駝峯式命名法:首字母小寫。eg:PersonInfo
文件命名
- 所有文件名統一使用小寫,首頁命名為index.xxx,文件名禁止特殊字符比如空格、$等。統一使用英文單詞或拼音縮寫,必須小寫。( 為了醒目,某些説明文件的文件名,可以使用大寫字母,比如README、LICENSE。 )
- 文件名包含多個單詞時,單詞之間建議使用半角的連詞線 ( - ) 分隔。
- 文件目錄結構嵌套層級不要過深
變量命名
命名方式 : 小駝峯式命名方法命名規範 : 類型+對象描述的方式,如果沒有明確的類型,就可以使前綴為名詞
| 表頭 | 表頭 | | -------- | -- | | array | a | | boolean | b | | function | fn | | int | i | | object | o | | regular | r | | string | s |
函數命名
命名方式 : 小駝峯方式 ( 構造函數使用大駝峯命名法 ) 命名規則 : 前綴為動詞
| 動詞 | 含義 | 返回值 | | --- | ------------------ | ----------------------------- | | can | 判斷是否可執行某個動作 ( 權限 ) | 函數返回一個布爾值。true:可執行;false:不可執行 | | has | 判斷是否含有某個值 | 函數返回一個布爾值。true:可執行;false:不可執行 | | is | 判斷是否為某個值 | 函數返回一個布爾值。true:可執行;false:不可執行 | | get | 獲取某個值 | 函數返回一個非布爾值 | | set | 設置某個值 | 無返回值、返回是否設置成功或者返回鏈式對象 |
例子:
// 是否可跳舞
function canDance(){
return true;
}
// 獲取工作
function getWork{
return this.work
}
常量命名
命名方法 : 全部大寫命名規範 : 使用大寫字母和下劃線來組合命名,下劃線用以分割單詞。
例子:
const PATH = "xxxx"
類命名
- 公共屬性和方法 : 同變量命名方式
- 私有屬性和方法 : 前綴為下劃線(_或#)後面跟公共屬性和方法一樣的命名方式
註釋規範
單行註釋和多行註釋的空格保存代碼時eslint會幫我們處理,不用手動加空格。
單行註釋 ( // )
- 單獨一行://(雙斜線)與註釋文字之間保留一個空格
- 在代碼後面添加註釋://(雙斜線)與代碼之間保留一個空格,並且//(雙斜線)與註釋文字之間保留一個空格。
- 註釋代碼://(雙斜線)與代碼之間保留一個空格
多行註釋 ( / 註釋説明 / )
- 若開始(/
*
和結束(*
/)都在一行,推薦採用單行註釋 - 若至少三行註釋時,第一行為/
*
,最後行為*
/,其他行以*
開始,並且註釋文字與*
保留一個空格。
函數(方法)註釋
函數(方法)註釋也是多行註釋的一種,但是包含了特殊的註釋要求
/**
* 函數説明
* @關鍵字
*/
常用註釋關鍵字
| 註釋名 | 語法 | 含義 | 示例 | | -------- | ----------------------- | --------- | ------------------------- | | @param | @param 參數名 {參數類型} 描述信息 | 描述參數的信息 | @param name {String} 傳入名稱 | | @return | @param 參數名 {參數類型} 描述信息 | 描述返回值的信息 | @param name {String} 傳入名稱 | | @author | @author 作者信息 [附屬信息:如郵箱] | 描述返回值的信息 | @author 李四 2022/12/16 | | @version | @version XX.XX.XX | 描述此函數的版本號 | @version 1.1.1 | | @example | @example 示例代碼 | 描述此函數的版本號 | |
文件目錄結構
同功能放在同一個文件目錄下,目錄結構不要嵌套過深,文件名語義化一些,方便後續維護。
- 文件夾名稱全部採用小寫+"-" 來隔開;
- 避免多層嵌套,單個項目中的目錄嵌套控制在最多三到四個層級內;
例子:
- src 開發目錄
- pages 視圖
- module-a 模塊A
- components 私有組件
- ComA.vue
- ComB.vue
- index.vue
- module-b 模塊B
- components 公共組件
- index.vue 導出所有組件
- header
- index.vue
- utils 這裏是以utils為後綴,JS工具庫
- index.js
- a.utils.js
- b.utils.js
- hooks 這裏是以hooks為後綴
- index.js
- a.hooks.js
- b.hooks.js
- service api請求,這裏是以api為後綴
- a.api.js 按照後端微服務進行劃分
- b.api.js
- constans 常量
通過對工具函數、hooks、api等加上後綴,更加容易區分引入的文件。
代碼規範
JS
JS/TS主流的大致有這幾種:
可以參考star最多的進行配置,幾乎覆蓋了JavaScript的每一項特性。
下面會就代碼層面作出闡述。
| 名稱 | 説明 |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 命名規範 | 1. 普通命名採用小駝峯式命名
2. 命名是複數的時候需要加s
3. 命名需要符合語義化,儘量減少縮寫的情況發生,做到見名知意,如果函數命名,可以採用加上動詞前綴 |
| 變量規範 | 1. 變量定義儘量使用const、let
2. 變量兜底 |
| 字符串 | 1. 統一使用單引號而不是雙引號,配置安裝eslint後,保存會自動格式化處理
2. 用字符串模板而不是'+'來拼接字符串
3. 不要使用不必要的轉義字符
4. 不要在字符串中用eval(),漏洞太多 |
| 數組 | 1. 用擴展運算符(...)做數組淺拷貝
2. 使用數組解構 |
| 對象 | 1. ES6 使用屬性值縮寫,將屬性的縮寫放在對象聲明的開頭
2. 對象淺拷貝時,更推薦使用擴展運算符 ...使用對象解構 |
| 函數 | 1. 函數參數使用默認值替代使用條件語句進行賦值
2. 函數參數越少越好,如果參數超過兩個,要使用 ES6 的解構語法,不用考慮參數的順序。把默認參數賦值放在最後
3. 儘量使用箭頭函數
4. 用命名函數表達式而不是函數聲明,函數聲明作用域會提升,降低了代碼可讀性和可維護性
5. 不要改參數,不要對參數重新賦值
6. 功能函數使用純函數,輸入一致,輸出結果永遠唯一
7. 優先使用函數式編程 |
| for循環 | 使用for循環過程中,數組的長度,使用一個變量來接收, 有利於代碼執行效率得到提高,而不是每走一次循環,都得重新計算數組長度|
Vue
遵循vue.js官方風格指南,https://vuejs.bootcss.com/style-guide/
組件
| 名稱 | 説明 |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 組件命名 | 1. 組件名為多個單詞,命名為組件用途,完整單詞的組件名(傾向於完整單詞而不是縮寫)
2. 文件名應該要麼始終是單詞大寫開頭 (PascalCase),要麼始終是橫線連接 (kebab-case),例如:todo-item或TodoItem
3. 基礎組件名, 應用特定樣式和約定的基礎組件 (也就是展示類的、無邏輯的或無狀態的組件) 應該全部以一個特定的前綴開頭,比如Base、App或V
4. 單例組件名,只應該擁有單個活躍實例的組件應該以The前綴命名,以示其唯一性
5. 緊密耦合的組件名, 和父組件緊密耦合的子組件應該以父組件名作為前綴命名
6. 組件名中的單詞順序, 組件名應該以高級別的 (通常是一般化描述的) 單詞開頭,以描述性的修飾詞結尾
7.模板中的組件名大小寫,對於絕大多數項目來説,在單文件組件和字符串模板中組件名應該總是PascalCase的——但是在DOM模板中總是kebab-case的
8. JS/JSX中的組件名大小寫,JS/JSX 中的組件名應該始終是 PascalCase 的,儘管在較為簡單的應用中只使用Vue.component進行全局組件註冊時,可以使用kebab-case字符串 |
| 組件數據 | 組件的data必須是一個函數 |
| props定義 | 1. prop定義應該儘量詳細
2. prop名大小寫,在聲明prop的時候,其命名應該始終使用camelCase,而在模板和JSX中應該始終使用kebab-case。 |
| v-for使用 | 1. 為v-for設置鍵值,儘量避免使用index作為key
2. 避免v-if和v-for用在一起 |
| 樣式 | 為組件樣式設置作用域,使用scoped屬性,使用BEM約定 |
| 私有property名 | 1. Vue使用前綴來定義其自身的私有 property
2. 推薦使用$ ,作為一個用户定義的私有property的約定,以確保不會和Vue自身相沖突 |
| 自閉合組件 | 在單文件組件、字符串模板和JSX中沒有內容的組件應該是自閉合的——但在DOM模板裏永遠不要這樣做 |
| attribute | 1. 多個attribute的元素應該分多行撰寫,每個attribute一行
2. 帶引號的attribute值 |
| 模板中簡單的表達式 | 組件模板應該只包含簡單的表達式,複雜的表達式則應該重構為計算屬性或方法 |
| 簡單的計算屬性 | 應該把複雜計算屬性分割為儘可能多的更簡單的property,計算屬性不能產生副作用 |
| 指令 | 指令縮寫 (用:表示 v-bind:、用@表示 v-on: 和用#表示 v-slot:) |
| 組件通信 | 應該優先通過prop和事件進行父子組件之間的通信,而不是this.$parent或變更prop |
| 事件、定時器 | 清除定時器或者事件監聽 |
| 代碼文件 | 開發過程中單個文件不允許超過600行,特別複雜的功能,文件不允許超過1000行 |
模板中的組件名大小寫
PascalCase相比kebab-case有一些優勢:
- 編輯器可以在模板裏自動補全組件名,因為PascalCase同樣適用於JavaScript
視覺上比 更能夠和單個單詞的HTML元素區別開來,因為前者的不同之處有兩個大寫字母,後者只有一個橫線 - 如果你在模板中使用任何非Vue的自定義元素,比如一個Web Component,PascalCase確保了你的Vue組件在視覺上仍然是易識別的
由於HTML是大小寫不敏感的,在DOM模板中必須仍使用kebab-case。
例子:
```
Prop名大小寫
我們單純的遵循每個語言的約定。在JavaScript中更自然的是camelCase。而在HTML中則是kebab-case。
文件目錄
| 名稱 | 説明 | | ---- | --------------------------------------- | | 資源 | 資源統一放置在 assets 文件夾下,資源以文件夾組織,文件夾名稱即模塊名稱 | | 公共方法 | 放置在utils內部 | | 樣式 | BEM命名規範 |
CSS
CSS檢查代碼規範
使用stylelint插件,規範則推薦使用stylelint-config-standard
下面簡單説下stylelint-config-standard使用
1. 安裝
yarn add -D stylelint stylelint-config-standard
2. 在項目的根目錄中創建一個配置文件.stylelintrc.json,內容如下:
{
"extends": "stylelint-config-standard"
}
3. 解決與prettier配置的衝突:
yarn add -D stylelint-config-prettier
4. 將下面配置複製到.stylelintrc.json中:
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"]
}
5. 在 git commit 階段進行檢測:
"lint-staged": {
"**/*": "prettier --write --ignore-unknown", // 格式化
"src/**.{js,jsx,ts,tsx}": "eslint --ext .js,.jsx,.ts,.tsx", // 對js文件檢測
"**/*.{less,css}": "stylelint --fix" // 對css文件進行檢測
},
BEM命名原則
- block:模塊,名字單詞間用-連接
- element:元素,模塊的子元素,以__與block連接
- modifier:修飾,模塊的變體,定義特殊模塊,以--與block連接
有效使用css選擇器
有效使用css選擇器,需遵循以下原則:
- 保持簡單,不要使用嵌套過多過於複雜的選擇器,選擇器嵌套應少於3級;
- 通配符和屬性選擇器效率最低,需要匹配的元素最多,儘量避免使用;
- 避免使用CSS表達式;
- 慎重選擇高消耗的樣式(高消耗屬性在繪製前需要瀏覽器進行大量計算),避免重繪重排;
- css選擇器中避免使用標籤名;
- 儘量使用縮寫屬性;
- 使用子選擇器;
- 0後面不帶單位;
- id和class,命名名稱語義化,不要過於簡單,防止模塊之間樣式互相影響;合理的使用id,一般情況下id不應該被用於樣式,並且id的權重很高,所以不使用id解決樣式的問題,而是使用class;
CodeReview常見代碼問題彙總
| 類別 | 描述 | 説明 |
| ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 文件 | 命名 | 1. 組件命名規範,儘量不要和現有組件或遠程組件重合,比如頁面裏使用組件時直接使用Table
2. 文件命名儘量語義化,如果沒有定製化的,文件命名不要太定製化,比如文件命名直接是fifth-floor
3. 文件中變量/方法命名語義化,方法名格式統一 |
| UI規範 | 刪除按鈕顏色 | 刪除類的操作按鈕顏色使用紅色 |
| vue | 代碼問題 | 1. 組件數據共享: 嵌套調用的組件聲明一個方法,直接返回當前模塊的數據,不要層層嵌套,通過$refs獲取,比如:組件裏定義一個getValues方法獲取數據
2. 生命週期鈎子:vue生命週期如果沒有依賴關係的話,儘量不要用async/await,可以把請求封裝成一個方法,生命週期中直接調用方法;如果有依賴關係的話就用,看場景
3. v-for中key值綁定,儘量不要綁定index,使用id,如果沒有id且不涉及添加刪除操作時,可以綁定index
4. 使用vue/composition-api時,相同變量以及方法考慮是否放在一起,方便看,看場景及個人習慣
5. 引入第三方工具包時儘量使用小的包,比如moment包換成dayjs
6. 使用動態路由(id)
7. 不建議this傳遞,問題排查容易出問題,即在其他頁面修改this裏的變量 |
| JS | 代碼問題 | 1. map/forEach: 注意區分兩者使用場景,不要隨便使用
2. 常量:頁面裏使用多次的字符統一使用常量映射,不要直接在頁面中使用字符
3. 代碼簡潔性:避免使用多次循環列表,如果列表數據很多會有性能問題,比如filter和map嵌套使用
4. 三元表達式: 使用時根據場景可以換成或
5. 否定前置
6. 空數據:接口返回數據為空兼容判斷
7. 數組遍歷for循環修改為for/of
8. dayjs/moment可以轉換一切時間形式為format,不止是時間戳還有標準時間
9. 空值合併運算符(??)使用: 當左側的操作數為 null 或者 undefined 時,返回其右側操作數,否則返回左側操作數
10. dayjs().format('YYYY-MM-DD HH:mm:ss')無參數時默認取的當前時刻; 有參數時可以考慮把參數提取出來
11. toString()可以寫為join(',')
12. 數據為空兼容:用或、?.表示
13. if switch可以考慮轉換為json map形式
14. a或b或c改為 [].includes()
15. try/catch捕獲錯誤異常console.dir(error); |
| ES6 | 代碼問題 | 1. map循環中可以使用解構的話換成解構,避免多層嵌套
2. 解構:能解構儘量解構,邊界值兼容處理 |
| CSS | 代碼問題 | 1. 類名:類名注意不要太簡單,直接取name/title/desc之類的,容易和其他人寫的類名衝突,比如別人寫了同名的類名沒有設置scoped或者全局類名,會影響自己的樣式 |
map/forEach説明
比如:
```
demo1
arr.map(({value = {}, ...item})=>{ return { ...item, ...value }})
demo2
const type = this.alarmType?.map(item => ({alarmTypeId: item})) || [];
demo3 push行為修改為map
this.evidence.printScreen = data.printScreen?.map(
({ id, title, value }) => ({
id,
title,
value,
url: ${imgUrlPre}${id}
,
})
);
```
dayjs.format(str)説明
dayjs.format(str) ,str抽離成下面形式
const DATE_FORMAT_TYPE = {
date: 'YYYY-MM-DD',
time: 'HH:mm:ss',
dateTime: 'YYYY-MM-DD HH:mm:ss',
};
相關資料: - BEM規範:https://getbem.com/naming/