分享一份公司前端程式碼規範(JS+TS + React),僅供參考

evan_feng發表於2020-03-20

前端程式碼規範(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

相關文章