從零到一建立前端規(guī)范
大廠技術 高級前端 Node進階
點擊上方 程序員成長指北,關注公眾號
回復1,加入高級Node交流群
本文適合打算建立前端規(guī)范的小伙伴閱讀
一、為什么需要規(guī)范
統(tǒng)一代碼規(guī)范的好處:
提高代碼整體的
可讀性、可維護性、可復用性、可移植性和可靠性,這會從根本上降低開發(fā)成本,也是最重要的一點。保證代碼的一致性:軟件系統(tǒng)中最重要的因素之一就是編碼的一致性。如果編碼風格一致,也
更加易于維護,因為團隊內任何人都可以快速理解并修改。提升團隊整體效率:開發(fā)人員通常需要花費大量的時間來解決代碼質量問題,如果都按照規(guī)范編寫,也有助于團隊
盡早發(fā)現問題,甚至完全預防問題,這將提高整個交付過程的效率。減少code review期間一系列的爭議,因為缺乏標準,在爭議過程中雙方很難妥協(沒少因為這事爭論過??)。
若不統(tǒng)一代碼規(guī)范,可能會造成的后果:
由于缺乏規(guī)范,導致
代碼風格不一,增加團隊成員間的心理負擔,極端情況下,某段代碼只有某個人能修改(俗稱屎山??)。團隊間協作更加困難:因為開發(fā)人員得
適應不同的風格,會導致效率低下(閱讀代碼是我們花費時間最多的地方)。在code review期間可能經常為類似的事情做過多的討論。
影響團隊的生產力和質量,嚴重的甚至會影響團隊和諧。
二、為什么依然有很多團隊缺乏規(guī)范
當開發(fā)人員被要求在短時間內完成任務時,通常會回避質量標準。
團隊中總是有一些有個性的人不會為了團隊去改變自己的習慣。
有些人在會議上就約定達成了一致,在會下依舊我行我素。
...
三、如何保持規(guī)范
我曾想通過會議討論的方式來制定規(guī)范,但效果卻差強人意。將失敗的原因總結為大致幾點:
在會議中,思維很容易發(fā)散,經常出現的情況是討論了很多,卻很難有實際性的成效,在
開發(fā)中依然有不少人選擇無視規(guī)則正式的會議往往很難組織,大家很難一起有空閑的時間來討論,一次/兩周 都很困難。
會議中對實際案例分析,
提出若干點優(yōu)化建議后,沒有對問題的優(yōu)先級和側重點進行劃分,導致實際效果并不好。還有一點也是我自己的原因,組織會議的能力有待提升...??
經歷了上述的挫敗之后,經過反復復盤總結,決定換一種方式來執(zhí)行:
對規(guī)范問題進行歸納分析并
通過文檔記錄(wiki等),尋找業(yè)內最佳解決方案,在團隊內進行統(tǒng)一。采用小步快跑的方式,有問題就解決問題,
按照優(yōu)先級和重要性進行排序劃分,依次將問題納入迭代,每個迭代重點解決其中幾個即好。本迭代的規(guī)范問題絕不留到下個迭代,防止堆積(當然,有時候還是得向項目經理妥協?????)。
在code review過程中嚴格把關,拒絕睜一只眼??閉一只眼??。
當團隊成員對具體某個規(guī)范有爭議時,及時討論并定出結論。
沒有規(guī)則只是為了規(guī)則,制定規(guī)范的目的并不是一定要按照某某標準來執(zhí)行,更多的是團隊成員達成一致即可。鼓勵大家大膽的質疑規(guī)則,若不能提高代碼的可讀性,可維護性,可復用性,可移植性和可靠性的規(guī)則都應該被受到質疑。
以身作則,船頭的方向不能偏航。
經過兩個月的迭代后,發(fā)現效果出奇的好??,大家的規(guī)范意識普遍增強,當遇到規(guī)范問題時也不再畏畏縮縮,而是大膽的拋出在群里討論。
四、開發(fā)者需要建立和遵守的規(guī)范
大致可以劃分成這幾個方向:
開發(fā)流程規(guī)范
代碼規(guī)范
git commit規(guī)范
項目文件結構規(guī)范
UI設計規(guī)范
開發(fā)流程規(guī)范
這里可能有小伙伴有疑問了,開發(fā)流程規(guī)范不是項目經理定的嗎???,跟我有什么關系?
這里想告訴大家的是,開發(fā)流程在一定程度上應該是由我們自己來掌控。不管是傳統(tǒng)開發(fā)的模式還是敏捷開發(fā)的模式,對于開發(fā)者來說核心依舊是高質高效的完成用戶提出的需求。
筆者曾見過不少開發(fā)者在拿到產品經理的需求后就開始急匆匆的寫代碼,以此來體現他們的高效,但往往卻因為需求理解不到位和前期代碼欠缺設計導致bug率高和返工。
如何找到適合自己的開發(fā)流程是需要依靠經驗來支撐的,需要反復總結和思考,最終達到高質高效完成的目的。
說一說筆者自己比較喜歡的開發(fā)流程:

在接收到需求后應第一時間去了解這個需求的背景是什么?這么做到底有沒有解決用戶的痛點?或者說用戶更深層次的需求是什么?如果團隊的產品經理經驗不豐富,往往可以在這個階段砍掉很多不合理的需求(這一點真的很重要)。
對于復雜大功能往往還需要進行技術方案調研和技術方案設計,并輸出詳細的設計文檔。涉及到細節(jié)上,則需要將數據流走向、組件設計等通過腦圖的形式呈現出來。
代碼規(guī)范之格式化規(guī)范
由于每個開發(fā)者的IDE不同,即使IDE相同也會因為每個人的配置不一樣導致格式化的結果不一樣。如何確保團隊內開發(fā)人員采用統(tǒng)一的格式化配置呢?
這里給推薦大家使用 prettier,它內置了一套格式化的規(guī)則,具體配置:
1). 安裝依賴:
npm install --save-dev --save-exact prettier
// or
yarn add --dev --exact prettie
2). 創(chuàng)建一個空配置文件,讓編輯器和其他工具知道你正在使用 Prettier:
echo {}> .prettierrc.jso
3). 創(chuàng)建一個.prettierignore文件,讓 Prettier CLI 和編輯器知道哪些文件不能格式化,example:
# Ignore artifacts:
dist
build
coverag
4). 配置編輯器(VScode為例)
IDE中安裝 Prettier-Code Formater 插件:

找到IDE中設置模塊,搜索 format On Save,勾上這個就可以了。

現在當我們 Ctrl + S 保存代碼時,插件就會幫助我們自動格式化了。
這里有小伙伴要問了,要是有人將沒有格式化的代碼提交上去怎么辦?
這時候就需要在 git commit 的階段自動將提交的代碼進行格式化,這里我們借助工具 husky,它主要可以幫助我們在 git 階段檢查提交消息、運行測試、檢查代碼等。沒接觸過的小伙伴可以去官網了解一下,配置如下:
安裝 husky 和 lint-staged:
npm install --save-dev husky lint-staged
npx husky install
npm set-script prepare "husky install"
npx husky add .husky/pre-commit "npx lint-staged"
// or
yarn add --dev husky lint-staged
npx husky install
npm set-script prepare "husky install"
npx husky add .husky/pre-commit "npx lint-staged
然后將以下內容添加到
package.json中:
{
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
}
}
lint-staged,lint-staged中的內容就是對暫存區(qū)的文件執(zhí)行格式化的命令。
eslintConfig)。prettier 和 eslint 會有一些配置上的沖突,這個時候需要安裝eslint-config-prettier 以使 ESLint 和 Prettier 相互配合,安裝完后在.eslintrc中配置(以Create-React-App為例):
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest",
"prettier"
]
}
這樣就可以用"prettier"的部分規(guī)則覆蓋前面的規(guī)則,讓它們都能正常工作。
代碼規(guī)范之JS/TS規(guī)范
JS/TS主流的大致有這幾種:
Airbnb JavaScript Style Guide
Google JavaScript Style Guide
Idiomatic JavaScript Style Guide
JavaScript Standard Style Guide
jQuery JavaScript Style Guide
比較推薦使用 Airbnb JavaScript Style Guide,它在 Github 上足有 12萬 star,幾乎覆蓋了 JavaScript 的每一項特性。
具體配置:
1). 安裝依賴
npm install eslint --save-dev
// or
yarn add eslint --de
npm init @eslint/config
// or
yarn create @eslint/confi
跟著終端中的提示,按照自身需求一步步選擇即可。
有了具體的規(guī)范后,我們同樣需要使用工具去約束:還是通過在git commit階段校驗,若不通過則取消提交。
package.json 中的 lint-staged ):
"lint-staged": {
"**/*": "prettier --write --ignore-unknown", //格式化
"src/*": "eslint --ext .js,.ts,.tsx" //進行eslint校驗
}
注意:這里如果選用的Typescript,則會默認使用
@typescript-eslint/parser解析器,官方為了追求更快的解析速度,并不會對.ts文件中的類型進行檢查,只會做語法檢測。如果需要對類型也進行檢測,需要在extends中加上plugin:@typescript-eslint/recommended-requiring-type-checking。
索性這里換了另一種方式:在
pre commit中執(zhí)行yarn run tsc,這里的意思是對項目中ts文件進行類型檢測,默認會讀取根目錄中的tsconfig.json配置。這種方式并不完美,它的弊端就在于
全量檢測,如果項目不大還好,若是項目代碼量夠多,檢測10-20s也是常有的事。
代碼規(guī)范之CSS規(guī)范
CSS檢查代碼規(guī)范使用 stylelint 插件,規(guī)范則推薦使用 stylelint-config-standard:
1). 安裝
npm install --save-dev stylelint stylelint-config-standar
.stylelintrc.json,內容如下:
{
"extends": "stylelint-config-standard"
prettier配置的沖突:
npm install --save-dev stylelint-config-prettier
4). 將下面配置復制到.stylelintrc.json中:
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"]
}
"lint-staged": {
"**/*": "prettier --write --ignore-unknown", //格式化
"src/**.{js,jsx,ts,tsx}": "eslint --ext .js,.jsx,.ts,.tsx", //對js文件檢測
"**/*.{less,css}": "stylelint --fix" //對css文件進行檢測
},
代碼規(guī)范之自定義其他規(guī)范
下面列一些團隊內定的其他規(guī)范:
(1)命名規(guī)范
變量的命名中應盡量減少縮寫的情況發(fā)生,做到見名知意。
// ?? 自我感覺良好的縮寫:
let rContent = 'willen';
// ?? 無需對每個變量都寫注釋,從名字上就看懂
let firstName = 'jackie';
// ?? 從命名無法知道返回值類型
function showFriendsList() {....} // // 無法辨別函數意圖,返回的是一個數組,還是一個對象,還是true or false?
// ?? 明確函數意圖,對于返回true or false的函數,最好以should/is/can/has開頭
function shouldShowFriendsList() {...}
function isEmpty() {...}
function canCreateDocuments() {...}
function hasLicense() {...}
function sendEmailToUser(user) {.... } //動詞開頭,函數意圖就很明
(2)寫注釋
在每個文件的頂部明確說明該組件做什么,有沒有業(yè)務理解難點等等,對業(yè)務特殊函數/變量也需要寫注釋
/**
* 導航頁面-右邊區(qū)域
*/
const Content=>()=>xxx
const MAX_INPUT_LENGTH = 8; //用于限制密碼輸入框
function Component(props) {
return (
<>
{/* 如果用戶沒有訂閱則不展示廣告 */}
{user.subscribed ? null : <SubscriptionPlans />}
</>
)
}
(3)變量兜底
// ?? 對于求值獲取的變量,沒有兜底
const { data } = getApiRequest();
data.map((s) => s.id); //沒有考慮data異常的情況,代碼一跑就爆炸
// ?? 對于求值變量,做好兜底
const { data = [] } = getApiRequest();
data.map((s) => s?.id); //沒有考慮data異常的情況,代碼一跑就爆炸
(4)輔助函數必須是純函數
// ?? 不要讓功能函數的輸出變化無常
function plusAbc(a, b, c) { // 這個函數的輸出將變化無常,因為api返回的值一旦改變,同樣輸入函數的a,b,c的值,但函數返回的結果卻不一定相同。
var c = fetch('../api');
return a+b+c;
}
// ?? 功能函數使用純函數,輸入一致,輸出結果永遠唯一
function plusAbc(a, b, c) { // 同樣輸入函數的a,b,c的值,但函數返回的結果永遠相同。
return a+b+c;
(5)優(yōu)先使用函數式編程
// ?? 使用for循環(huán)編程
for(i = 1; i <= 10; i++) {
a[i] = a[i] +1;
}
// ?? 使用函數式編程
let b = a.map(item => ++item
(6)優(yōu)先使用函數式組件
除非需要用到錯誤邊界,否則函數式組件應該是首選方法。
(7)組件復雜度
如果一個組件做的事情太多,應適當提取一些邏輯,將其拆分為更小的組件。
如果提取的組件很復雜,則需要依照一定的規(guī)則和條件一一提取它。
代碼行數并不是一個客觀的衡量標準,更多是需要考慮責任劃分和抽象。
(8)用錯誤邊界
當需要對大量數據進行渲染處理時,需要通過錯誤邊界組件對其進行降級處理。
function Component() {
return (
<Layout>
<ErrorBoundary>
<CardWidget />
</ErrorBoundary>
<ErrorBoundary>
<FiltersWidget />
</ErrorBoundary>
<div>
<ErrorBoundary>
<ProductList />
</ErrorBoundary>
</div>
</Layout>
)
(9)props參數傳遞
props一層層傳遞一直是我們很頭疼的一個問題,最核心的問題是不清楚props是從哪個初始組件傳來的,以及props中到底有哪些東西,上下文是什么?
// A.tsx
interface AProps {
param: string;
}
const A = ({ param }: AProps) => {
return <B param = {param} />;
};
// ?? 上下文清晰
// B.tsx
const B = ({ param }: { param: AProps['param'] }) => {
return <div>hello world</div>;
};
(10)props傳參數量
如果超過 5 個props,就該考慮是否拆分該組件。在某些情況下,這是需要對組件進行重構的標志。
注意:組件使用的props越多,重新渲染的理由就越多。
(11)避免嵌套三元運算符
三元運算符在第一級之后變得難以閱讀,雖然看起來節(jié)省了代碼空間,但最好在代碼中明確意圖,保持良好的閱讀性。
// ?? 不夠清晰,要是再嵌套一層兩層呢
isSubscribed ? (
<ArticleRecommendations />
) : isRegistered ? (
<SubscribeCallToAction />
) : (
<RegisterCallToAction />
)
// ?? 將判斷邏輯進行拆分
function CallToActionWidget({ subscribed, registered }) {
if (subscribed) {
return <ArticleRecommendations />
}
if (registered) {
return <SubscribeCallToAction />
}
return <RegisterCallToAction />
}
function Component() {
return (
<CallToActionWidget
subscribed={subscribed}
registered={registered}
/>
)
(12)將列表組件封裝成獨立組件
// ?? 列表渲染和其他邏輯雜糅在一起
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
{articles.map(article => (
<div>
<h3>{article.title}</h3>
<p>{article.teaser}</p>
<img src={article.image} />
</div>
))}
<div>You are on page {page}</div>
<button onClick={onNextPage}>Next</button>
</div>
)
}
// ?? 將列表組件提取出來,一目了然
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
<ArticlesList articles={articles} />
<div>You are on page {page}</div>
<button onClick={onNextPage}>Next</button>
</div>
)
}
(13)避免嵌套渲染函數
// ?? 不要將其定義在渲染函數組件中
function Component() {
function renderHeader() {
return <header>...</header>
}
return <div>{renderHeader()}</div>
}
// ?? 將其抽離到獨立的組件中去
import Header from '@modules/common/components/Header'
function Component() {
return (
<div>
<Header />
</div>
)
}
(14)組件/函數導入導出
// ?? 在文件頭部導入,順序依次為: 第三方庫 > 公共組件/方法 > 非公共部分組件/方法
import React from 'react'
import _ from 'loadsh'
import Header from '@components/header'
import Content from './Content'
// ?? 在底部導出
export { Content, Header }
export default Componen
項目文件結構規(guī)范
在項目初期若不重視,到了后期就是到處天馬行空,你很難在期望的目錄下找到你想要的文件。
- src 開發(fā)目錄
- pages 視圖
- module-a 模塊A
- components 私有組件
- ComA.tsx
- ComB.tsx
- index.module.less
- index.tsx
- Content.tsx
- module-b 模塊B
- components 公共組件
- index.ts 導出所有組件
- header
- index.tsx
- index.module.less
- User.tsx
- useGetBaseInfo.hooks.ts
- routers 路由文件
- store redux中的數據
- utils 這里是以utils為后綴
- index.ts
- a.utils.ts
- b.utils.ts
- hooks 這里是以hooks為后綴
- index.ts
- a.hooks.ts
- b.hooks.ts
- styles 靜態(tài)資源文件
- service api請求,這里是以api為后綴
- a.api.ts 按照后端微服務進行劃分
- b.api.ts
- constans 常量
通過對工具函數、hooks、api 等加上后綴,更加容易區(qū)分引入的文件。
Git commit規(guī)范
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer
?? type主要有以下幾種類型:
feat: 一個新特性
fix: 修復bug
docs: 文檔修改
style: 不影響代碼含義的更改(空格、格式、缺少分號等)
refactor: 代碼重構
perf: 優(yōu)化性能
test: 測試用例修改
chore: 對構建過程或輔助工具和庫的更改,例如文檔生成
?? scope:可以是影響范圍的任何內容。
?? subject:包含對更改的簡潔描述,規(guī)則:
使用陳述語句
第一個字母不要大寫
末尾沒有點 (.)
?? body:commit 具體修改內容, 可以分為多行,應該包括改變的動機,并與以前的行為進行對比。
?? footer: 一些備注, 通常是修復的 bug 的鏈接。
截取一張開源庫的 commit,example:

有了規(guī)范后,我們需要通過工具去約束:commitlint。它要做的就是在我們每次提交 git commit 的時候,都會幫我們檢查 commit message 是否符合一定的規(guī)范,如果不符合,就讓這次提交失敗。
# 安裝 commitlint cli 和 conventional config
npm install --save-dev @commitlint/{config-conventional,cli}
# Windows:
npm install --save-dev @commitlint/config-conventional @commitlint/cli
配置要使用的 commitlint 規(guī)則
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
加入到husky中:
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
or
yarn husky add .husky/commit-msg 'yarn commitlint --edit $1
UI設計規(guī)范
優(yōu)秀的開發(fā)者應該反向推動UI的規(guī)范,并且能夠支撐UI規(guī)范的落地。
UI 規(guī)范的最大好處就是能夠提質提效:
在開發(fā)者的角度,與設計規(guī)范同步形成研發(fā)資產,避免重復造輪子;
在測試的角度,能夠避免重復的無意義走查;
在UI設計師的角度,減少設計成本,提高設計效率,可以快速承接新需求;
站在產品角度,提高產品迭代與優(yōu)化效率,降低試錯成本;
站在用戶角度,解決用戶體驗一致性。
那到底應該怎么去推動UI規(guī)范?我的做法是先讓設計師去給出一系列的規(guī)范,沒有相關規(guī)范就拉上產品經理一起制定規(guī)范。然后前端建立一套自己的組件庫,再將組件庫提供給UI設計師,以此來相互監(jiān)督是否達成了規(guī)范協議。
五、總結
Node 社群
我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關的交流、學習、共建。下方加 考拉 好友回復「Node」即可。
“分享、點贊、在看” 支持一下
