前端程式碼規範
最近組內經常進行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/