多是你須要的 React + TypeScript 50 條規範和經驗

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

1. 註釋

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

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

(2) 模塊的註釋

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

(3) 業務代碼註釋

/*業務代碼註釋*/
複製代碼

(4) 變量註釋

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

2. 引用組件順序

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

3. 引號

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

4. 縮進

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

5. 分號

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

6. 括號

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

// not good
if (condition) doSomething();

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

7. 空格

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

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

while (condition){
}

function funcName(){
}

// good
if (condition) {
}

while (condition) {
}

function funcName() {
}

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

while(condition) {
}

(function() {
})();

// good
if (condition) {
}

while (condition) {
}

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

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

8. 換行

  • 每一個獨立語句結束後必須換行。
  • 在函數聲明、函數表達式、函數調用、對象建立、數組建立、for 語句等場景中,不容許在 , 或 ; 前換行
// 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];

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

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


// good
if (condition) {
    ...
} else {
    ...
}

try {
    ...
} catch (e) {
    ...
} finally {
    ...
}
複製代碼

9. 數組、對象

  • 對象屬性名不須要加引號;
  • 對象以縮進的形式書寫,不要寫在一行;
  • 數組最後不要有逗號。
  • 對象最後要有逗號。
// 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];
複製代碼

10. 命名

  • 類名: 大駝峯式風格,字母和數字,例如:AbcTest。禁止漢字、特殊符號,禁止非大駝峯式風格。html

  • 函數名: 小駝峯式風格,字母和數字,例如:abcTest。禁止漢字、特殊符號,禁止非小駝峯式風格,例如snake_case等。前端

  • 變量名: 同函數名。java

  • 常量: 全大寫風格,大寫字母、數字和下劃線,單詞之間如下劃線分隔,例如:ABC_TEST。禁止漢字、特殊符號、小寫字母。react

  • 使用 onXxx 形式做爲 props 中用於回調的屬性名稱。git

interface IProps {
  onClose?: () => void;
  onOk?: (item: Record<string, any>) => void;
}
複製代碼
  • 組件內的事件函數使用 handle 開頭尾,handleCheckBtn。
  • 使用 withXxx 形式的詞做爲高階組件的名稱。
  • 接口命名前面帶上 I 表示 interface
interface IProps {}
interface IState {}
複製代碼

11. 類型斷言

// 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;
  }
}

複製代碼

12. interface聲明順序

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

interface iProps {
  readonly x: number;
  readonly y: number;
  name: string;
  age: number;
  height?: number;
  [propName: string]: any;
}
複製代碼

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

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

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

const people: Readonly<Todo> = {
    title: 'todo list',
    name: chenfeng;
};
title name屬性就是隻讀的了
複製代碼
  • Required 的做用是將傳入的屬性變爲必選項
interface iPeople {
    title?: string;
    name?: string;
}

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

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

查看更多github

14. ts一些好用的小tips

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

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

15. 規範其餘

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

16. 僅當初始 state 須要從 props 計算獲得的時候,纔將 state 的聲明放在構造函數中,其它狀況下使用靜態屬性聲明 state,而且通常狀況下不要將 prop 傳給 state,

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

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

17. 渲染默認值

  • 添加非空判斷能夠提升代碼的穩健性,例如後端返回的一些值,可能會出現不存在的狀況,應該要給默認值.
// bad
render(){
  {name}
}

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


複製代碼
  • 還有一種狀況,就是原本後端應該返回一個數組給你,可是數據庫取不到數據,可能後端給你返回了null,而後前端null.length。這樣就gg了
// 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,
});

複製代碼

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

例如一些地方,不肯定這個變量裏面到底有什麼,但本身以爲有,就瘋狂的...,最明顯的就是後端返回了一個對象給你,前端拿到以後判斷都不判斷直接data.dataList.forEach()web

// 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() 
}
複製代碼

19. 數據格式轉換

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

20. 判斷條件真假

js 中如下爲假,其餘狀況爲真

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

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

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

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

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

// bad
this.props.app.openid;
this.state.time

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

23. input 輸入框使用 trim()

// bad
let searchContent = form.search.value;

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

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

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

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

25. 使用 react-router

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

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


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

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

複製代碼

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

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

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

// 建議

|- api
  |- pageA
    |- index.js
    |- aaa.js
    |- bbb.js
  |- pageB
    |- index.js
    |- aaa.js
    |- bbb.js
    |- ccc.js
複製代碼

27. 組件嵌套過深

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

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

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

  }
}
複製代碼

29. 業務代碼裏面的異步請求須要 try catch

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

30. setState有三種用法

// 對象
this.setState({

})

// 函數,通常是用於在setState以前作一些操做
this.setState(
  () => {
    // TODO
    console.log('')
    return {
      a:300
    }
  }
)

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

31. setState多是同步的

  • setState 在react裏的合成事件和鉤子函數中是「異步」的。
  • setState 在原生事件和 setTimeout 中是同步的。

32. 不要在 setState 前面加 await

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

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

33. 阻止事件默認行爲

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

34. 在 componentWillUnmount 裏面去除反作用的函數

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

35. key

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

//good
this.state.dataAry.map(item => <span key={item.id} />);
複製代碼

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

//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]);
  }
}

複製代碼

37. 第三方庫函數的使用

  • 用 try catch 包裹,防止第三方庫的出現錯誤,致使整個程序崩潰
/* * 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
}
複製代碼

38. 防止 xss 攻擊

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

39. 在組件中獲取真實 dom

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

  render() {
    return <input type="text" ref={this.inputRef} />; } componentDidMount() { this.inputRef.current.focus(); } } 複製代碼

40. 減小魔法數字

  • 寫代碼的時候儘可能減小一些未知含義的數字,儘可能用英文單詞。例如type === 0的時候作了一些操做,讓人不知因此然。
// 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,
}

複製代碼

41. 若是須要優化 react 性能(通常用不到)

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

// good
class Message extends PureComponent {
  render() {
    return <span>{this.state.message}</span>;
  }
}
複製代碼
  • 重寫 shouldComponentUpdate 方法,在 shouldComponentUpdate 裏面根據 state,props 是否有改變來判斷是否須要從新渲染.若是組件繼承了 PureComponent 就不必再重寫 shouldComponentUpdate 方法
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;
}
複製代碼

42. Event 事件對象類型

不少小夥伴用了好久的ts,都不知道經常使用 Event 事件對象類型:

ClipboardEvent<T = Element> 剪貼板事件對象

DragEvent<T = Element> 拖拽事件對象

ChangeEvent<T = Element> Change 事件對象

KeyboardEvent<T = Element> 鍵盤事件對象

MouseEvent<T = Element> 鼠標事件對象

TouchEvent<T = Element> 觸摸事件對象

WheelEvent<T = Element> 滾輪事件對象

AnimationEvent<T = Element> 動畫事件對象

TransitionEvent<T = Element> 過渡事件對象

import { MouseEvent } from 'react';

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

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

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

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

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

44. 代碼細粒度的思考

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

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

我的以爲if else 嵌套深看起來也不會太難受,難受的是,項目迭代久以後,本身都忘記曾經寫過這些代碼,並且類型多或者不肯定有什麼類型,是否後期還會加的狀況下,改起來就很是複雜了,並且很容易踩坑和背鍋。 用配置取代if嵌套,大概就是抽離一個config.ts出來,裏面放一些配置。

例如你的業務代碼裏面,會根據不一樣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, typeItem> = {
  '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)
  }
})

複製代碼

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

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

// bad
  renderHeader = () => {
    return (<div />)
  }
  renderBody = () => {
    return (<div />)
  }
  renderFooter = () => {
    return (<div />)
  }
  render(){
    return(
      <div>
        renderHeader()
        renderBody()
        renderFooter()
      </div>
    )
  }
複製代碼

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

// good
 function RenderHeader(props) =  {
    return (<div />)
  }
 function RenderBody(props) =  {
    return (<div />)
  }
 function RenderFooter(props) =  {
    return (<div />)
  }
class Component extends React.Component<iProps, iState>{  
  render () {
    return(
      <div>
        <RenderHeader />
        <RenderBody />
        <RenderFooter />
      </div>
    )
  }
}
複製代碼

47. a標籤安全問題

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

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

var newWindow = window.open();
newWindow.opener = null;
複製代碼

48. void 0 替代undefined

clearSessioin = () => {
	
  req.session.userName = undefined;
  
  req.session.userName = void 0
}
複製代碼

49. 前端不要操做cookie

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

50. 代碼檢查插件

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

參考

相關文章
相關標籤/搜索