你需要的 React + TypeScript 50 條規範和經驗


你需要的 React + TypeScript 50 條規範和經驗



這篇文章沒有對錯之分,肯定也有不完善的地方,結合了自己日常開發和經驗。可以讓你書寫代碼更具嚴謹性,希望看完之後有所幫助。本文字數4000+ ,看完本文大概需半小時。

1. 註釋

(1) 文件頂部的註釋,包括描述、作者、日期

<code>/**
* @description xxxxxx
* @author chengfeng
* @since 19/05/21
*/
複製代碼/<code>

(2) 模塊的註釋

<code>/**
* 拷貝數據
* @param {*} data 要拷貝的源數據
* @param {boolean} [isDeep=false] 是否深拷貝,默認淺拷貝
* @return {*} 返回拷貝後的數據
*/
複製代碼/<code>

(3) 業務代碼註釋

<code>/*業務代碼註釋*/
複製代碼/<code>

(4) 變量註釋

<code>interface IState {
// 名字
name: string;
// 電話
phone: number;
// 地址
address: string;
}
複製代碼/<code>

2. 引用組件順序

  • 先引用外部組件庫,,再引用當前組件塊級組件, 然後是 common 裡的公共函數庫最後是 css 樣式
<code>import * as React from 'react';
import { Dropdown, Menu, Icon } from 'antd';
import Header from './Header';
import toast from 'common/toast';
import './index.less';
複製代碼/<code>

3. 引號

  • 使用單引號,或者 es6 的反引號

4. 縮進

  • 使用兩個空格
<code>const handleCheck = () => {
onCancel && onCancel();
onClose && onClose();
};
複製代碼/<code>

5. 分號

  • 除了代碼塊的以外的每個表達式後必須加分號。

6. 括號

下列關鍵字後必須有大括號(即使代碼塊的內容只有一行):if, else, for, while, do, switch, try, catch, finally, with。

<code>// not good
if (condition) doSomething();

// good
if (condition) {
doSomething();
}
複製代碼/<code>

7. 空格

  • 二元和三元運算符兩側必須有一個空格,一元運算符與操作對象之間不允許有空格。
<code>// bad
++ x;
y ++;
z = x?1:2;

// good
++x;
y++;
z = x ? 1 : 2;
複製代碼/<code>
  • 用作代碼塊起始的左花括號 { 前必須有一個空格。
<code>// bad
if (condition){
}

while (condition){
}

function funcName(){
}

// good
if (condition) {
}

while (condition) {
}

function funcName() {
}

複製代碼/<code>
  • if / else / for / while / function / switch / do / try / catch / finally 關鍵字後,必須有一個空格。
<code>// bad
if(condition) {
}

while(condition) {
}

(function() {
})();

// good
if (condition) {
}

while (condition) {
}

(function () {
})();
複製代碼/<code>
  • 在對象創建時,屬性中的 : 之後必須有空格,: 之前不允許有空格。
<code>// bad
var obj = {
a : 1,
b:2,
c :3
};

// good
var obj = {
a: 1,
b: 2,
c: 3
};
複製代碼/<code>

8. 換行

  • 每個獨立語句結束後必須換行。
  • 在函數聲明、函數表達式、函數調用、對象創建、數組創建、for 語句等場景中,不允許在 , 或 ; 前換行
<code>// bad
var obj = {
a: 1
, b: 2
, c: 3,
};

function test()
{
...
}
for (const key in object)
{

if (object.hasOwnProperty(key)) {
const element = object[key];

}
}
// good
var obj = {
a: 1,
b: 2,
c: 3,
};

function test() {
...
}

for (const key in object) {
if (object.hasOwnProperty(key)) {
const element = object[key];

}
}
複製代碼/<code>
  • 下列關鍵字後:else, catch, finally 不需要換行
<code>// bad
if (condition) {
...
}
else {
...
}

try {
...
}
catch (e) {
...
}
finally {
...
}


// good
if (condition) {

...
} else {
...
}

try {
...
} catch (e) {
...
} finally {
...
}
複製代碼/<code>

9. 數組、對象

  • 對象屬性名不需要加引號;
  • 對象以縮進的形式書寫,不要寫在一行;
  • 數組最後不要有逗號。
  • 對象最後要有逗號。
<code>
// bad
const a = {
'b': 1
};

const a = {b: 1};

const a = {
b: 1,
c: 2
};
const arr = [1, 2, 3, 4,];

// good
const a = {
b: 1,
c: 2,
};


const arr = [1, 2, 3, 4];
複製代碼/<code>

10. 命名

  • 類名: 大駝峰式風格,字母和數字,例如:AbcTest。禁止漢字、特殊符號,禁止非大駝峰式風格。
  • 函數名: 小駝峰式風格,字母和數字,例如:abcTest。禁止漢字、特殊符號,禁止非小駝峰式風格,例如snake_case等。
  • 變量名: 同函數名。
  • 常量: 全大寫風格,大寫字母、數字和下劃線,單詞之間以下劃線分隔,例如:ABC_TEST。禁止漢字、特殊符號、小寫字母。
  • 使用 onXxx 形式作為 props 中用於回調的屬性名稱。
<code>interface IProps {
onClose?: () => void;
onOk?: (item: Record<string>) => void;
}
複製代碼/<string>/<code>
  • 組件內的事件函數使用 handle 開頭尾,handleCheckBtn。
  • 使用 withXxx 形式的詞作為高階組件的名稱。
  • 接口命名前面帶上 I 表示 interface
<code>interface IProps {}
interface IState {}
複製代碼/<code>

11. 類型斷言

<code>// bad
function getLength(something: string | number): number {
return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

// bad
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}

// good
function getLength(something: string | number): number {
if (typeof something === 'string') {
return something.length;
} else {
return something.toString().length;
}
}

複製代碼/<string>/<string>/<code>

日常用到比較多的是四種,只讀參數放第一位,必選參數第二位,可選參數次之,不確定參數放最後

<code>interface iProps {
readonly x: number;
readonly y: number;
name: string;
age: number;
height?: number;
[propName: string]: any;

}
複製代碼/<code>

13. ts好用的相關工具泛型

  • Record<string> 用這個來聲明對象結構的類型/<string>
<code>用於定義一個javascript的對象,key是字符串,value是任意類型
const people:Record<string> = {
name: 'chengfeng',
age: 10
}
複製代碼/<string>/<code>
  • Partial 作用是將傳入的屬性變為可選項.
<code>interface iPeople {
title: string;
name: string;
}

const people: Partial<ipeople> = {
title: 'Delete inactive users',
};
定義的結構可以是接口iPeople的任意key
複製代碼/<ipeople>/<code>
  • Readonly 作用是將傳入的屬性變為變成只讀
<code>interface iPeople {
title: string;
name: string;
}

const people: Readonly<todo> = {

title: 'todo list',
name: chenfeng;
};
title name屬性就是隻讀的了
複製代碼/<todo>/<code>
  • Required 的作用是將傳入的屬性變為必選項
<code>interface iPeople {
title?: string;
name?: string;
}

const people1: Props = { title: 'ts' }; // OK

const people22: Required<ipeople> = { title: 'ts' }; // Error: property 'name' missing
複製代碼/<ipeople>/<code>

查看更多

14. ts一些好用的小tips

  • keyof
<code>interface iPeople {
name: string;
age: number
}

type T = keyof iPeople // -> "name" | "age"
複製代碼/<code>
  • in
<code>type Keys = "a" | "b"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any }
複製代碼/<code>

15. 規範其他

  • 不要使用 var 聲明變量
  • 不會被修改的變量使用 const 聲明
  • 去除聲明但未被引用的代碼
  • 禁止在代碼裡使用 debug
  • 不允許有空的代碼塊

16. 僅當初始 state 需要從 props 計算得到的時候,才將 state 的聲明放在構造函數中,其它情況下使用靜態屬性聲明 state,並且一般情況下不要將 prop 傳給 state,

<code>// bad
constructor (){
this.setState({ people: this.props.people })
}

// good
state: IState = {
people: {},
};
複製代碼/<code>

17. 渲染默認值

  • 添加非空判斷可以提高代碼的穩健性,例如後端返回的一些值,可能會出現不存在的情況,應該要給默認值.
<code>// bad 

render(){
{name}
}

// good
render(){
{!!name || '--'}
}


複製代碼/<code>
  • 還有一種情況,就是本來後端應該返回一個數組給你,但是數據庫取不到數據,可能後端給你返回了null,然後前端null.length。這樣就gg了
<code>// bad
const { list, totalCount } = await getPeopleList(keyword, page, pageSize);
list 可能是null或者undefined
list.length將直接導致前端報錯

this.setState({
status: STATUS.READY,
apps: list,
total: totalCount,
page: page,
});


// good
const { list, totalCount } = await getPeopleList(keyword, page, pageSize);
this.setState({
status: STATUS.READY,
apps: list || [],
total: totalCount || 0,
page: page,
});

複製代碼/<code>

18. 不確定的屬性,最後卻瘋狂的用...訪問不存在的屬性

例如一些地方,不確定這個變量裡面到底有什麼,但自己覺得有,就瘋狂的...,最明顯的就是後端返回了一個對象給你,前端拿到之後判斷都不判斷直接data.dataList.forEach()

<code>// bad
const data = await getPeopleList(keyword, page, pageSize);
data.dataList.forEach() // 直接掛了

// good
const data = await getPeopleList(keyword, page, pageSize);
if (data && data.dataList && Array.isArray(data.dataList) {
data.dataList.forEach()
}
複製代碼/<code>

19. 數據格式轉換

  1. 把字符串轉整型可以使用+號
<code>let maxPrice = +form.maxPrice.value;
let maxPrice = Number(form.maxPrice.value);
複製代碼/<code>
  1. 轉成 boolean 值用!!
<code>let mobile = !!ua.match(/iPhone|iPad|Android|iPod|Windows Phone/);
複製代碼/<code>

20. 判斷條件真假

js 中以下為假,其他情況為真

  • false
  • null
  • undefined
  • 0
  • '' (空字符串)
  • NaN

21. 簡單組件可以使用函數代替

<code>// bad
class Listing extends React.Component {
render() {
return
{this.props.hello}
;
}
}

// good
function Listing({ hello }) {
return
{hello}
;
}
複製代碼/<code>

22. 對於常用的屬性進行緩存

<code>// bad
this.props.app.openid;
this.state.time

// good
const { app } = this.props;
const { time } = this.state;
console.log(app.openid)
複製代碼/<code>

23. input 輸入框使用 trim()

<code>// bad
let searchContent = form.search.value;


// good
let searchContent = form.search.value.trim();
複製代碼/<code>

24. 使用 location 跳轉前需要先轉義

<code>// bad
window.location.href = redirectUrl + '?a=10&b=20';

// good
window.location.href = redirectUrl + encodeURIComponent('?a=10&b=20');
複製代碼/<code>

25. 使用 react-router

<code>
// bad
import { withRouter, RouteComponentProps } from 'react-router-dom';

export interface IProps extends RouteComponentProps {}
class App extends React.Component<iprops> {}
export default withRouter(App);


// good
import { withRouter, RouteComponentProps } from 'react-router-dom';

class App extends React.Component<iprops>, AppStates> {}
export default withRouter(App);

複製代碼/<iprops>/<iprops>
/<code>

26. 同時開發,數據請求 api 目錄 git 衝突目錄方案

  • 在 api 目錄下新建一個目錄,目錄對應一級 tab,這個目錄內放置一個 index.js ,最後把二級 tab 組件所使用的 api 請求都在這個 index.js 內引入。
<code>// 目前 


|- api
|- pageA.ts
|- pageB.ts

// 建議

|- api
|- pageA
|- index.js
|- aaa.js
|- bbb.js
|- pageB
|- index.js
|- aaa.js
|- bbb.js
|- ccc.js
複製代碼/<code>

27. 組件嵌套過深

  • 組件一般不要超過三層,最多四層,層級過深可能會導致數據傳遞過深,在做一些顆粒度比較細的操作的時候,處理起來較為繁瑣,可以使用 redux 等狀態管理工具替代。

28. 代碼過濾掉你沒考慮到的情況

  • 例如一個函數,你只想操作字符串,那你必須在函數開頭就只允許參數是字符串
<code>function parse (str:string){
if (typeof(str) === 'string' ) {

}
}
複製代碼/<code>

29. 業務代碼裡面的異步請求需要 try catch

  • ajax 請求,使用 try catch,錯誤提示後端返回,並且做一些失敗後的狀態操作例如進入列表頁,我們需要一個 loading 狀態,然後去請求數據,可是失敗之後,也需要把 loading 狀態去掉,把 loading 隱藏的代碼就寫在 finally 裡面。
<code>getStudentList = async () => {
try {
this.setState({
loading: true,
isEmpty: false
});
await getStudentList({});
} catch (e) {
// TODO
console.log(e)
} finally {
// 失敗之後的一些兜底操作
this.setState({
loading: false,
isEmpty: true
});
}
};
複製代碼/<code>

30. setState有三種用法

<code>// 對象
this.setState({

})

// 函數,一般是用於在setState之前做一些操作
this.setState(
() => {
// TODO

console.log('')
return {
a:300
}
}
)

// 第二個參數,一般是用於在setState之後做一些操作
this.setState({
a:300
}, () => {
// TODO
})
複製代碼/<code>

31. setState可能是同步的

  • setState 在react裡的合成事件和鉤子函數中是“異步”的。
  • setState 在原生事件和 setTimeout 中是同步的。

32. 不要在 setState 前面加 await

  • setState 前面也是可以帶 await 的,會變成同步設置狀態,但這是一種巧合,不確定未來哪個版本就不支持了,為了遵循 react 框架的設計原則,我們使用回掉函數的形式。
<code>// bad
func = async (name, value, status) => {
await this.setState({
name
});
// TODO
};

// good
func = (name, value, status) => {
this.setState(
{
name
},
() => {
// TODO
}
);
};
複製代碼/<code>

33. 阻止事件默認行為

  • 在 React 中你不能通過返回 false 來阻止默認行為。必須明確調用 preventDefault 。

34. 在 componentWillUnmount 裡面去除副作用的函數

  • 清除 EventListener
  • 中止數據請求
  • 清除定時器

35. key

  • 對於組件中的 key 優化,起到最大化重用 dom
<code>//bad
this.state.dataAry.map((item, index) => {
return ;
});

//good
this.state.dataAry.map(item => );
複製代碼/<code>

36. for-in 中一定要有 hasOwnProperty 的判斷(即禁止直接讀取原型對象的屬性)

<code>//bad
const arr = [];
const key = '';

for (key in obj) {
arr.push(obj[key]);
}

//good
const arr = [];
const key = '';

for (key in obj) {
if (obj.hasOwnProperty(key)) {
arr.push(obj[key]);
}
}

複製代碼/<code>

37. 第三方庫函數的使用

  • 用 try catch 包裹,防止第三方庫的出現錯誤,導致整個程序崩潰
<code>/*
* Echart 用於代繪製圖表,但當其自身發生錯誤時,可能影響到業務代碼的執行
*/
// bad
const iniDom = document.getElementById('init-container');
const echartObj = echarts.init(iniDom);
this.setState(
{
echartObj
},
() => {
const { echartObj } = this.state;

// 更新圖表
echartObj.setOption(CHART_CONFIG, true);
}
);

// good
try {
const iniDom = document.getElementById('init-container');
const echartObj = echarts.init(iniDom);
this.setState(
{
echartObj
},
() => {
const { echartObj } = this.state;
// 更新圖表
echartObj.setOption(CHART_CONFIG, true);
}
);
} catch (error) {
// TODO
}
複製代碼/<code>

38. 防止 xss 攻擊

  • input,textarea 等標籤,不要直接把 html 文本直接渲染在頁面上,使用 xssb 等過濾之後再輸出到標籤上;
<code>import { html2text } from 'xss';
render(){
dangerouslySetInnerHTML={{
__html: html2text(htmlContent)
}}
/>
}
複製代碼

39. 在組件中獲取真實 dom

  • 使用 16 版本後的 createRef()函數
<code>class MyComponent extends React.Component<iprops> {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}

render() {
return ;
}

componentDidMount() {
this.inputRef.current.focus();
}
}
複製代碼/<iprops>/<code>

40. 減少魔法數字

  • 寫代碼的時候儘量減少一些未知含義的數字,儘量用英文單詞。例如type === 0的時候做了一些操作,讓人不知所以然。
<code>// bad
if (type !== 0) {
// TODO
}

// good
const STATUS: Record<string> = {
READY: 0,
FETCHING: 1,
FAILED: 2
};

if (type === STATUS.READY) {
// TODO
}

// best
enum STATUS {
// 就緒
READY = 0,

// 請求中
FETCHING = 1,
// 請求失敗
FAILED = 2,
}

複製代碼/<string>/<code>

41. 如果需要優化 react 性能(一般用不到)

  • 如果組件的 state 和 props 都是簡單類型,可以繼承 PureComponent 而不是 Component
<code>import { Component, PureComponent } from 'react';
// bad
class Message extends Component {
render() {
return {this.state.message};
}
}

// good
class Message extends PureComponent {
render() {
return {this.state.message};
}
}
複製代碼/<code>
  • 重寫 shouldComponentUpdate 方法,在 shouldComponentUpdate 裡面根據 state,props 是否有改變來判斷是否需要重新渲染.如果組件繼承了 PureComponent 就沒必要再重寫 shouldComponentUpdate 方法
<code>import { isReactPropsEqual, isReactStateEqual } from '@fe/common/lib/equal';
shouldComponentUpdate(nextProps:IProps, nextState:IState) {
if (isReactStateEqual(nextState,this.state) && isReactPropsEqual(nextProps,this.props)) {
return false;
}
return true;
}

複製代碼/<code>

42. Event 事件對象類型

很多小夥伴用了很久的ts,都不知道常用 Event 事件對象類型:

ClipboardEvent 剪貼板事件對象

DragEvent 拖拽事件對象

ChangeEvent Change 事件對象

KeyboardEvent 鍵盤事件對象

MouseEvent 鼠標事件對象

TouchEvent 觸摸事件對象

WheelEvent 滾輪事件對象

AnimationEvent 動畫事件對象

TransitionEvent 過渡事件對象

<code>import { MouseEvent } from 'react';

interface IProps {
onClick(event: MouseEvent<htmldivelement>): void;
}
複製代碼/<htmldivelement>/<code>

43. 使用私有屬性取代state狀態

對於一些不需要控制ui的狀態屬性,我們可以直接綁到this上, 即私有屬性,沒有必要弄到this.state上,不然會觸發渲染機制,造成性能浪費 例如請求翻頁數據的時候,我們都會有個變量。

<code>// bad
state: IState = {
pageNo:1,
pageSize:10
};

// good
queryParams:Record<string> = {
pageNo:1,
pageSize:10
}
複製代碼/<string>/<code>

44. 代碼細粒度的思考

總結四句話。我們在寫組件或者函數的的時候,工具函數和業務邏輯抽離,表單校驗和業務抽離、事件函數和業務抽離,ajax和業務抽離。 例如有些頁面是通過location.href跳轉的,我們有些業務邏輯等都是放到didmountMount,但是後期改需求,可能要用react-router進行跳轉,可能要改的邏輯就會很多了,所以函數抽離出來,需求更新就少改一點代碼。 如果還不確定如何劃分函數的細粒度,我有個建議。使用過兩次以上的代碼,要抽離組件或者函數,兩次的可以不用

45. if else 等判斷太多了,後期難以維護。

個人覺得if else 嵌套深看起來也不會太難受,難受的是,項目迭代久之後,自己都忘記曾經寫過這些代碼,而且類型多或者不確定有什麼類型,是否後期還會加的情況下,改起來就非常複雜了,而且很容易踩坑和背鍋。 用配置取代if嵌套,大概就是抽離一個config.ts出來,裡面放一些配置。

<code>例如你的業務代碼裡面,會根據不同url參數,代碼會執行不同的邏輯.
/info?type=wechat&uid=123456&
const qsObj = qs(window.location.url)
const urlType = qsObj.type
// bad
if (urlType === 'wechat') {
doSomeThing()
} else if () {
doSomeThing()
} else if () {
doSomeThing()
} else if () {
doSomeThing()
}

// good
config.t
const urlTypeConfig: Record<string> = {
'wechat': { // key 就是對應的type
name: 'wechat',
show: ['header', 'footer', 'wechat'] // 展示什麼,可能是異步的
pession: ['admin'], // 權限是什麼,可能是異步的
},
'zhifubao': { // key 就是對應的type
name: 'zhifubao',
show: ['header', 'footer', 'zhifubao'] // 展示什麼,可能是異步的

pession: ['admin'], // 權限是什麼,可能是異步的
},
}

// 業務邏輯
const qsObj = qs(window.location.url)
const urlType = qsObj.type
urlTypeConfig.forEach(item => {
if(urlType === item.type) {
doSomeThing(item.show)
}
})

複製代碼/<string>/<code>

46. 不要使用renderXXX,要使用函數式組件

發現團隊一些小夥伴為了減少render函數里面的代碼量,會把一些元素拆分到函數里面。

<code>// bad
renderHeader = () => {
return (
)
}
renderBody = () => {
return (
)
}
renderFooter = () => {
return (
)
}
render(){
return(

renderHeader()
renderBody()
renderFooter()

)
}
複製代碼/<code>

更好的辦法,是用函數式組件取代在當前組件裡面寫方法

<code>// good
function RenderHeader(props) = {
return (
)
}
function RenderBody(props) = {
return (
)
}
function RenderFooter(props) = {
return (
)
}
class Component extends React.Component<iprops>{
render () {
return(

<renderheader>
<renderbody>
<renderfooter>

)
}
}
複製代碼/<iprops>/<code>

47. a標籤安全問題

使用a標籤打開一個新窗口過程中的安全問題。新頁面中可以使用window.opener來控制原始頁面。如果新老頁面同域,那麼在新頁面中可以任意操作原始頁面。如果是不同域,新頁面中依然可以通過window.opener.location,訪問到原始頁面的location對象

在帶有target="_blank"的a標籤中,加上rel="noopener"屬性。如果使用window.open的方式打開頁面,將opener對象置為空。

<code>var newWindow = window.open();
newWindow.opener = null;
複製代碼/<code>

48. void 0 替代undefined

<code>clearSessioin = () => {
\t
req.session.userName = undefined;

req.session.userName = void 0
}
複製代碼/<code>

49. 前端不要操作cookie

在做一些前後端鑑權的時候,後端應該開啟domain,secure,httponly嚴格模式,禁止前端操作cookie,防止csrf攻擊。

50. 代碼檢查插件

我們可以使用構建工具繼承 husky eslint tslint lint-stage prettier來規範代碼。

  • eslint-config-prettier
  • eslint-plugin-prettier
  • eslint-plugin-react
  • tslint-react
  • tslint-plugin-prettier
  • tslint-config-prettier
  • 團隊開發工作流

參考

  • airbnb
  • imweb代碼規範
  • 如何無痛降低 if else 麵條代碼複雜度
  • 你真的理解setState嗎?

來源鏈接:https://juejin.im/post/5ce24f8ae51d45106477bd45

"
/<code>


分享到:


相關文章: