前端程式碼規範(TS + React)
背景:為使團隊程式碼風格儘量統一和規範,方便cr和查詢問題,結合過去一段時間的經驗,針對react + typescript提出程式碼規範,後續在實踐過程中可持續優化此規範。
javascript規範
使用單引號或es6反引號,使用兩個空格作為縮排
句末使用新增分號(buchongzidongtinajia)
前端程式碼是否只使用camelCase?(建議)
若轉換前後端欄位,可新增攔截器:
import camelcaseKeys from 'camelcase-keys';
import snakeCaseKeys from 'snakecase-keys';
const handleResponse = (response: AxiosResponse): AxiosResponse => {
return camelcaseKeys(response.data, { deep: true }) as unknown as AxiosResponse;
}
export const handleRequest = (request: AxiosRequestConfig): AxiosRequestConfig => {
if (request.data) {
request.data = snakeCaseKeys(request.data , { deep: true });
}
if (request.params) {
request.params = snakeCaseKeys(request.params , { deep: true });
}
return request;
}
export const productFeedsBaseURL = '/api';
export const productFeedsRequest: AxiosInstance = Axios.create({
// baseURL: `http://www.freehard.top${productFeedsBaseURL}`,
baseURL: '/api',
withCredentials: true
});
productFeedsRequest.interceptors.request.use(handleRequest);
productFeedsRequest.interceptors.response.use(handleResponse, handleErr);
複製程式碼
空格
二元和三元運算子兩側必須有一個空格,一元運算子與操作物件之間不允許有空格。
用作程式碼塊起始的左花括號 { 前必須有一個空格。
if / else / for / while / function / switch / do / try / catch / finally 關鍵字後,必須有一個空格。
在物件建立時,屬性中的 : 之後必須有空格,: 之前不允許有空格。
圓括號內側不留空格;圓括號內的大括號前後,冒號和分號後空一格
單個元件檔案程式碼不超過400行,超過時考慮拆分元件;單函式程式碼不超過50行
程式碼註釋
單行註釋//與註釋內容之間空一格 //bad commment // good comment
TODO FIXME: 大寫並在冒號後與內容之間空一格,建議配置VSCode外掛TODO Highlight使用。(建議)
避免拼寫錯誤,建議使用VSCode 外掛 Code Spell Checker(建議)
模組匯入
優先匯入第三方模組(node_modules 中的),再引入當前專案元件,;第三方模組匯入程式碼段和業務模組匯入程式碼段之間空一行,import 程式碼段與之後的業務程式碼之間空一行。
import React, { PureComponent } from 'react'
import { Checkbox, Button, Card, Spin } from 'antd'
import VoiceCollect from './VoiceCollect'
import { AudioPlayerProvider } from '../../components/AudioPlayer'
import * as API from '../../api'
import styles from './index.scss'
import { tips } from '../../common/tools'
import { autoCatch } from '../../decorators'
const CheckboxGroup = Checkbox.Group
複製程式碼
匯入路徑過長時使用webpack配置別名
沒有使用到的模組和變數應該刪除
優先使用const宣告,const > let > var
換行操作符前置(建議)
React規範
基本規則
每個檔案只包含一個React元件 無狀態元件允許一個檔案包含多個元件
命名
檔名:使用PascalCase命名。eg: ReservationCard.jsx, ReservationCard.tsx
引用命名:React元件使用PascalCase命名,例項使用camelCase命名
元件命名:元件名稱應該和檔名一致。如果在目錄中的元件應該使用index.jsx/index.tsx作為檔名,並使用資料夾名稱作為元件名。
其他非元件的小寫命名(redux, service)
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';
// bad
const ReservationItem = <ReservationCard />;
// good
const reservationItem = <ReservationCard />;
// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer';
複製程式碼
對齊
多行屬性採用縮排,屬性可以放在一行就保持在一行中
// bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// good
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// 如果元件的屬性可以放在一行就保持在當前一行中
<Foo bar="bar" />
複製程式碼
引號
JSX字串屬性採用雙引號,其他採用單引號
// bad
<Foo bar='bar' />
// good
<Foo bar="bar" />
// bad
<Foo style={{ left: "20px" }} />
// good
<Foo style={{ left: '20px' }} />
複製程式碼
空格
始終在自閉合標籤前面新增一個空格
// bad
<Foo/>
// very bad
<Foo />
// bad
<Foo
/>
// good
<Foo />
複製程式碼
屬性
屬性名稱始終使用camelCase
屬性值為true時,省略改屬性的賦值
屬性前後不要空格
// bad
<Foo
UserName="hello"
phone_number={12345678}
hidden={true}
loading={ loading }
/>
// good
<Foo
userName="hello"
phoneNumber={12345678}
hidden
loading={loading}
/>
複製程式碼
括號
使用括號包裹多行JSX標籤
// bad
render() {
return <MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>;
}
// good
render() {
return (
<MyComponent className="long body" foo="bar">
<MyChild />
</MyComponent>
);
}
// good, when single line
render() {
const body = <div>hello</div>;
return <MyComponent>{body}</MyComponent>;
}
複製程式碼
標籤
當標籤沒有子元素時,始終使用自閉合標籤
多行屬性時,關閉標籤應另起一行
// bad
<Foo className="stuff"></Foo>
// good
<Foo className="stuff" />
// bad
<Foo
bar="bar"
baz="baz" />
// good
<Foo
bar="bar"
baz="baz"
/>
複製程式碼
class元件成員排布順序
按照 普通成員, state,生命週期函式,自定義函式,render函式的順序編寫.
class SameCheck extends PureComponent<Prop, State> {
algorithms: Algorithm = []
readonly state: State = {
sampleA: '',
sampleB: '',
algorithms: [],
submiting: false,
result: [],
audioBlob: null,
formIniting: false
}
componentDidMount() {
this.getInfo()
}
handleVoiceAChange = (voiceKey: string) => {
this.setState({ sampleA: voiceKey })
}
render() {}
}
複製程式碼
使用React.Fragment
// bad
<React.Fragment>
<div></div>
<h3></h3>
</React.Fragment>
// good
<>
<div></div>
<h3></h3>
</>
複製程式碼
使用箭頭函式,不使用bind
// good
handleClick = () => { }
// bad
handleClick() { }
<div onClick={this.handleClick.bind(this)}></div>
複製程式碼
不允許直接修改state, state應該只讀,使用setState修改
// bad
this.state.arr.push(xxx)
this.forceUpdate()
// good
this.setState({ arr: this.state.arr.concat([xxx]) })
複製程式碼
避免記憶體洩漏,元件銷燬時清除註冊的事件,定時器。
儘量使用函式式元件配合hooks,減少使用class元件(建議)
只在最頂層使用hook,不要在迴圈,條件或巢狀函式中呼叫Hook
推薦使用eslint外掛 eslint-plugin-react-hooks
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
複製程式碼
useEffect的使用注意事項
可參考資料:
useEffect完全指南
使用hooks請求資料
react hooks依賴實踐指南
複製程式碼
避免陷入死迴圈
當effect中更新了state時會引起元件重新渲染,元件重新渲染又會出發effect,無限迴圈。
給useEffect第二個引數傳入[],只在元件mount的時候觸發effect。
// bad
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
});
return (
...
);
}
// good
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
}, []);
return (
...
);
}
複製程式碼
完整的列出useEffect的依賴,不要欺騙react
// bad
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
// good
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, [count]);
return <h1>{count}</h1>;
}
複製程式碼
typescript規範
有jsx程式碼的ts檔案字尾名使用.tsx,沒有的用ts
型別名使用PascalCase,列舉值使用PascalCase,列舉成員大寫,介面前不要加I
在每個檔案中,型別定義應該放在最前面
儘量減少使用any作為型別,型別應儘量詳細(建議)
interface宣告順序 只讀引數放第一位,必選引數第二位,可選引數次之,不確定引數放最後
interface Props {
readonly x: number;
readonly y: number;
name: string;
age: number;
height?: number;
[propName: string]: any;
}
複製程式碼
使用ts內建工具泛型(建議)Record
type Record<K extends keyof any, T> = {
[P in K]: T;
};
複製程式碼
可用來宣告物件結構的型別。
type Foo = Record<'a' | ‘b’, string>
const foo: Foo = {a: '1'} // 正確
const foo: Foo = {b: '1'} // 錯誤,因為 key 不為 a
const foo: Foo = {a: 1} // 錯誤,因為 value 的值不是 string 型別
Partial
type Partial<T> = {
[P in keyof T]?: T[P];
};
複製程式碼
將傳入的介面的必選屬性變為可選屬性。
interface iPeople {
title: string;
name: string;
}
const people: Partial<iPeople> = {
title: 'Delete inactive users',
};
複製程式碼
Required
type Required<T> = {
[P in keyof T]-?: T[P];
};
複製程式碼
將傳入的介面的可選屬性變為必選屬性
interface iPeople {
title?: string;
name?: string;
}
const people: Required<iPeople> = { title: 'ts' }; // Error: property 'name' missing
複製程式碼
Readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
複製程式碼
將傳入的介面的屬性變為只讀屬性
interface iPeople {
title: string;
name: string;
}
const people: Readonly<iPeople> = {
title: 'todo list';
name: 'ts';
};
// title name屬性就是隻讀的了
複製程式碼
Pick
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
複製程式碼
從傳入的介面的中提取一部分屬性作為新的介面。
interface iPeople {
name: string;
age: number;
gender: number;
}
const pickPerson: Pick<iPeople, 'name' | 'age'> = {name: 'test', age: 22};
複製程式碼
Exclude
type Exclude<T, U> = T extends U ? never : T;
複製程式碼
排除掉T中可以賦值給U的型別。
type ExcludeType = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;
// ExcludeType = 'b' | 'd'
複製程式碼
Extract
type Extract<T, U> = T extends U ? T : never;
複製程式碼
提取出T中可以賦值給U的型別。
type ExtractType = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;
// ExcludeType = 'a' | 'c'
複製程式碼
Omit
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
複製程式碼
基於T生成新的type,其中排除掉了在K中的屬性。
interface iPeople {
name: string;
age: number;
gender: number;
}
type OmitPeople = Omit<iPeople, 'name'>;
// OmitPeople = { age: number, gender: number}
複製程式碼
減少“魔數”
寫程式碼的時候儘量減少一些未知含義的數字或者布林值,儘量用英文單詞。
// bad
if (type !== 0) {
// TODO
}
// good
const STATUS: Record<string, any> = {
READY: 0,
FETCHING: 1,
FAILED: 2
};
if (type === STATUS.READY) {
// TODO
}
// best
enum STATUS {
// 就緒
READY = 0,
// 請求中
FETCHING = 1,
// 請求失敗
FAILED = 2,
}
複製程式碼
使用as作為型別斷言,不要使用<>
interface Foo {
bar: number;
bas: string;
}
// bad
const foo = <Foo>{};
foo.bar = 123;
foo.bas = 'hello';
// good
const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';
複製程式碼
S