溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解Typescript代碼

發(fā)布時(shí)間:2021-10-20 16:20:14 來源:億速云 閱讀:117 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“如何理解Typescript代碼”,在日常操作中,相信很多人在如何理解Typescript代碼問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”如何理解Typescript代碼”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

一、基礎(chǔ)規(guī)范

(1)常量

常量必須命名, 在做邏輯判斷的時(shí)候,也不允許直接對(duì)比沒有命名的常量。

  •  錯(cuò)誤的書寫 

switch(num){        case 1:          ...        case 3:          ...        case 7:          ...   }   if(x === 0){        ...   }

上述的例子中,根本不知道1 3 7 對(duì)應(yīng)的是什么意思,這種寫法就基本上沒有可讀性。

  •  正確的寫法   

enum DayEnum {         oneDay = 1,         threeDay = 3,         oneWeek = 7,     }     let num  = 1;     switch(num){         case DayEnum.oneDay:         ...         case DayEnum.threeDay:         ...         case DayEnum.oneWeek:         ...     }    const RightCode = 0;    if(x === RightCode)

從上述正確的寫法可以看出來,常量有了命名,在switch或者if等邏輯判斷的時(shí)候,我們可以從變量名得知常量的具體含義,增加了可讀性。

(2)枚舉

除了常量枚舉外,在Typescript的編譯階段,枚舉會(huì)生成一個(gè)maping對(duì)象,如果不是字符串枚舉,甚至?xí)梢粋€(gè)雙向的mapping。因此在我們的業(yè)務(wù)代碼中,有了枚舉,就不需要一個(gè)與枚舉值相關(guān)的數(shù)組。

  •  錯(cuò)誤的寫法 

enum FruitEnum {         tomato = 1,         banana =  2,         apple = 3  }  const FruitList = [    {       key:1,       value: 'tomato'    },{       key:2,       value: 'banana'    },{       key:3,       value: 'apple'    }  ]

這里錯(cuò)誤的原因是冗余,我們要得到一個(gè)FruitList,并不需要new一個(gè),而是可以直接根據(jù)FruitEnum的枚舉來生成一個(gè)數(shù)組,原理就是我們之前所說的Typescript的枚舉,除了常量枚舉外,在編譯的時(shí)候是會(huì)生成一個(gè)map對(duì)象的。

  •  正確的寫法 

enum FruitEnum {      tomato = 1,      banana =  2,      apple = 3  }  const FruitList = Object.entries(FruitEnum)

上述就是正確的寫法,這種寫法不僅僅是不冗余,此外,如果修改了枚舉的類型,我們只要直接修改枚舉,這樣衍生的數(shù)組也會(huì)改變。

除此之外,字符串枚舉值和字符串是有本質(zhì)區(qū)別的,在定義類型的時(shí)候請(qǐng)千萬注意,要不然會(huì)讓你寫的代碼很冗余。

  •  錯(cuò)誤的用法 

enum GenderEnum{    'male' = '男生',    'female' = '女生'  }  interface IPerson{     name:string     gender:string  }  let bob:IPerson = {name:"bob",gender:'male'}  <span>{Gender[bob.gender as keyof typeof GenderEnum]}</span>

上述的錯(cuò)誤的原因就是IPerson的類型定義中,gender不應(yīng)該是string,而應(yīng)該是一個(gè)枚舉的key,因此,在將string轉(zhuǎn)枚舉值的時(shí)候,必須增加一個(gè)as keyof typeof GenderEnum的斷言。

  •   正確的寫法 

enum GenderEnum{    'male' = '男生',    'female' = '女生'  }  interface IPerson{     name:string     gender:keyof typeof GenderEnum  }  let bob:IPerson = {name:"bob",gender:'male'}  <span>{Gender[bob.gender]}</span>

上述 就是正確的寫法,字符串枚舉和字符串類型是有 明顯區(qū)別的,當(dāng)某個(gè)變量需要使用到枚舉時(shí),不能將他定義成string。

(3)ts-ignore & any

Typescript中應(yīng)該嚴(yán)格禁止使用ts-ignore,ts-ignore是一個(gè)比any更加影響Typescript代碼質(zhì)量的因素。對(duì)于any,在我的項(xiàng)目中曾一度想把a(bǔ)ny也禁掉,但是有一些場(chǎng)景中是需要使用any的,因此沒有粗魯?shù)慕筧ny的使用。但是絕大部分場(chǎng)景下,你可能都不需要使用any.需要使用any的場(chǎng)景,可以case by case的分析。

  •  錯(cuò)誤使用ts-ignore的場(chǎng)景 

//@ts-ignore    import Plugin from 'someModule' //如果someModule的聲明不存在   Plugin.test("hello world")

上述就是最經(jīng)典的使用ts-ignore的場(chǎng)景,如上的方式使用了ts-ignore.那么Typescript會(huì)認(rèn)為Plugin的類型是any。正確的方法通過declare module的方法自定義需要使用到的類型.

  •  正確的方法 

import Plugin from 'someModule'  declare module 'someModule' {      export type test = (arg: string) => void;  }

在module內(nèi)部可以定義聲明,同名的聲明遵循一定 的合并原則,如果要擴(kuò)展三方模塊,declare module是很方便的。

同樣的大部分場(chǎng)景下,你也不需要使用any,部分場(chǎng)景下如果無法立刻確定某個(gè)值的類型,我們可以 用unknown來代替使用any。

any會(huì)完全失去類型判斷,本身其實(shí)是比較危險(xiǎn)的,且使用any就相當(dāng)于放棄了類型檢測(cè),也就基本上放棄了typescript。舉例來說:

let fish:any = {         type:'animal',         swim:()=> {             }  }  fish.run()

上述的例子中我們調(diào)用了一個(gè)不存在的方法 ,因?yàn)槭褂昧薬ny,因此跳過了靜態(tài)類型檢測(cè),因此是不安全的。運(yùn)行時(shí)會(huì)出錯(cuò),如果無法立刻確定某個(gè)值的類型,我們可以 用unknown來代替使用any。

let fish:unknown = {        type:'animal',        swim:()=> {            }  }  fish.run() //會(huì)報(bào)錯(cuò)

unkonwn是任何類型的子類型,因此跟any一樣,任意類型都可以賦值給unkonwn。與any不同的是,unkonwn的變量必須明確自己的類型,類型收縮或者類型斷言后,unkonwn的變量才可以正常使用其上定義的方法和變量。

簡(jiǎn)單來說,unkonwn需要在使用前,強(qiáng)制判斷其類型。

(4)namespace

Typescript的代碼中,特別是偏業(yè)務(wù)的開發(fā)中,你基本上是用不到namespace的。此外module在nodejs中天然支持,此外在es6(next)中 es module也成為了一個(gè)語言級(jí)的規(guī)范,因此Typescript官方也是推薦使用module。

namespace簡(jiǎn)單來說就是一個(gè)全局對(duì)象,當(dāng)然我們也可以把namespace放在module中,但是namespace放在module中也是有問題的。

  •  錯(cuò)誤的方法 

//在一個(gè)shapes.ts的模塊中使用  export namespace Shapes {      export class Triangle {        /* ... */      }      export class Square {        /* ... */      }  }  //我們使用shapes.ts的時(shí)候  //shapeConsumer.ts  import * as shapes from "./shapes";  let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
  •  正確的方法(直接使用module) 

export class Triangle {  /* ... */  }  export class Square {  /* ... */  }

上述直接使用module,就是正確的方法,在模塊系統(tǒng)中本身就可以避免變量命名重復(fù),因此namespace是沒有意義的。

(5)限制函數(shù)參數(shù)的個(gè)數(shù)

在定義函數(shù)的時(shí)候,應(yīng)該減少函數(shù)參數(shù)的個(gè)數(shù),推薦不能超過3個(gè)。

  •  錯(cuò)誤的用法 

function getList(searchName:string,pageNum:number,pageSize:number,key1:string,key2:string){     ...  }

不推薦函數(shù)的參數(shù)超過3個(gè),當(dāng)超過3個(gè)的時(shí)候,應(yīng)該使用對(duì)象來聚合。

  • 正確的用法 

interface ISearchParams{     searchName:string;     pageNum:number;     pageSize:number;     key1:string;     key2:string;  }   function getList(params:ISearchParams){  }

同樣的引申到React項(xiàng)目中,useState也是同理

const [searchKey,setSearchKey] = useState('');  const [current,setCurrent] = useState(1)  const [pageSize,setPageSize] = useState(10)  //錯(cuò)誤的寫法  const [searchParams,setSearchParams] = useState({     searchKey: '',     current:1,     pageSize:10  })  //正確的寫法

(6)module模塊盡量保證無副作用

請(qǐng)不要使用模塊的副作用。要保證模塊的使用應(yīng)該是先import再使用。

  •  錯(cuò)誤的方法 

//Test.ts  window.x = 1;  class Test{  }  let test = new Test()  //index.ts  import from './test'  ...

上述在index.ts中import的模塊,其調(diào)用是在test.ts文件內(nèi)部的,這種方法就是import了一個(gè)有副作用的模塊。

正確的方法應(yīng)該是保證模塊非export變量的純凈,且調(diào)用方在使用模塊的時(shí)候要先import,后調(diào)用。

  •  正確的方法 

//test.ts  class Test{     constructor(){        window.x = 1     }  }  export default Test  //index.ts  import Test from './test'  const t = new Test();

(7)禁止使用!.非空斷言

非空斷言本身是不安全的,主觀的判斷存在誤差,從防御性編程的角度,是不推薦使用非空斷言的。

  •  錯(cuò)誤的用法 

let x:string|undefinedundefined = undefined  x!.toString()

因?yàn)槭褂昧朔强諗嘌?,因此編譯的時(shí)候不會(huì)報(bào)錯(cuò),但是運(yùn)行的時(shí)候會(huì)報(bào)錯(cuò).

比較推薦使用的是optional chaining。以?.的形式。

(8)使用typescript的內(nèi)置函數(shù)

typescript的很多內(nèi)置函數(shù)都可以復(fù)用一些定義。這里不會(huì)一一介紹,常見的有Partial、Pick、Omit、Record、extends、infer等等,如果需要在已有的類型上,衍生出新的類型,那么使用內(nèi)置函數(shù)是簡(jiǎn)單和方便的。此外還可以使用 聯(lián)合類型、交叉類型和類型合并。

  •  聯(lián)合類型 

//基本類型  let x:number|string  x= 1;  x = "1"
//多字面量類型   let type:'primary'|'danger'|'warning'|'error' =  'primary'

值得注意的是字面量的賦值。

let type:'primary'|'danger'|'warning'|'error' =  'primary'  let test = 'error'  type = test  //報(bào)錯(cuò)  let test = 'error' as const   type =  test //正確
  •  交叉類型 

interface ISpider{     type:string     swim:()=>void  }  interface IMan{     name:string;     age:number;  }  type ISpiderISpiderMan = ISpider & IMan  let bob:ISpiderMan  = {type:"11",swim:()=>{},name:"123",age:10}
  •  類型合并

最后講一講類型合并,這是一種極其不推薦的方法。在業(yè)務(wù)代碼中,不推薦使用類型合并,這樣會(huì)增加代碼的閱讀復(fù)雜度。類型合并存在很多地方。class、interface、namespace等之間都可以進(jìn)行類型合并,以interface為例:

interface Box {    height: number;    width: number;  }  interface Box {    scale: number;  }  let box: Box = { height: 5, width: 6, scale: 10 };

上述同名的interface Box是會(huì)發(fā)生類型合并的。不僅interface和 interface可以類型合并,class和interface,class和namesppace等等都可能存在同名類型合并,在業(yè)務(wù)代碼中個(gè)人不推薦使用類型合并。

(9)封裝條件語句以及ts的類型守衛(wèi)

  • 錯(cuò)誤的寫法 

if (fsm.state === 'fetching' && isEmpty(listNode)) {   // ...  }
  •  正確的寫法 

function shouldShowSpinner(fsm, listNode) {       return fsm.state === 'fetching' && isEmpty(listNode);  }     if (shouldShowSpinner(fsmInstance, listNodeInstance)) {       // ...     }

在正確的寫法中我們封裝了條件判斷的邏輯成一個(gè)獨(dú)立函數(shù)。這種寫法比較可讀,我們從函數(shù)名就能知道做了一個(gè)什么判斷。

此外封裝條件語句也可以跟ts的自定義類型守衛(wèi)掛鉤。來看一個(gè)最簡(jiǎn)單的封裝條件語句的自定義類型守衛(wèi)。

function IsString (input: any): input is string {       return typeof input === 'string';  }  function foo (input: string | number) {       if (IsString(input)) {          input.toString() //被判斷為string       } else {          }  }

在項(xiàng)目中合理地使用自定義守衛(wèi),可以幫助我們減少很多不必要的類型斷言,同時(shí)改善代碼的可讀性。

(10)不要使用非變量

不管是變量名還是函數(shù)名,請(qǐng)千萬不要使用非命名,在業(yè)務(wù)中我就遇到過這個(gè)問題,后端定義了一個(gè)非命名形式的變量isNotRefresh:

let isNotRefresh = false  //是否不刷新,否表示刷新

isNotRefresh表示不刷新,這樣定義的變量會(huì)導(dǎo)致跟這個(gè)變量相關(guān)的很多邏輯都是相反的。正確的形式應(yīng)該是定義變量是isRefresh表示是否刷新。

let isRefresh = false  //是否刷新,是表示刷新

二、函數(shù)式

個(gè)人非常推薦函數(shù)式編程,主觀的認(rèn)為鏈?zhǔn)秸{(diào)用優(yōu)于回調(diào),函數(shù)式的方式又優(yōu)于鏈?zhǔn)秸{(diào)用。近年來,函數(shù)式編程日益流行,Ramdajs、RxJS、cycleJS、lodashJS等多種開源庫都使用了函數(shù)式的特性。本文主要介紹一下如何使用ramdajs來簡(jiǎn)化代碼。

(1)聲明式和命令式

個(gè)人認(rèn)為函數(shù)聲明式的調(diào)用比命令式更加簡(jiǎn)潔,舉例來說:

//命令式  let names:string[] = []  for(let i=0;i<persons.length;i++){          names.push(person[i].name)  }  //聲明式  let names = persons.map((item)=>item.name)

從上述例子我們可以看出來,明顯函數(shù)調(diào)用聲明式的方法更加簡(jiǎn)潔。此外對(duì)于沒有副作用的函數(shù),比如上述的map函數(shù),完全可以不考慮函數(shù)內(nèi)部是如何實(shí)現(xiàn)的,專注于編寫業(yè)務(wù)代碼。優(yōu)化代碼時(shí),目光只需要集中在這些穩(wěn)定堅(jiān)固的函數(shù)內(nèi)部即可。

(2)Ramdajs

推薦使用ramdajs,ramdajs是一款優(yōu)秀的函數(shù)式編程庫,與其他函數(shù)式編程庫相比較,ramdajs是自動(dòng)柯里化的,且ramdajs提供的函數(shù)從不改變用戶已有數(shù)據(jù)。

來自最近業(yè)務(wù)代碼中的一個(gè)簡(jiǎn)單的例子:

/**     * 獲取標(biāo)簽列表     */    const getList = async () => {        pipeWithP([            () => setLoading(true),            async () =>                request.get('', {                    params: {action: API.getList},                }),            async (res: IServerRes) => {                R.ifElse(                  R.isEqual(res.message === 'success'),                  () => setList(res.response.list);                )();            },            () => setLoading(false)        ])();    };

上述是業(yè)務(wù)代碼中的一個(gè)例子,利用pipe可以使得流程的操作較為清晰,此外也不用定義中間變量。

再來看一個(gè)例子:

let persons = [        {username: 'bob', age: 30, tags: ['work', 'boring']},        {username: 'jim', age: 25, tags: ['home', 'fun']},        {username: 'jane', age: 30, tags: ['vacation', 'fun']}      ]

我們需要從這個(gè)數(shù)組中找出tags包含fun的對(duì)象。如果用命令式:

let NAME = 'fun'  let person;  for(let i=0;i<persons.length;i++){     let isFind = false     let arr = persons[i].tags;     for(let j = 0;j<arr.length;j++){        if(arr[i] === NAME){           isFind = true           break;        }     }     if(isFind){        personperson = person[i]        break;     }  }

我們用函數(shù)式的寫法可以簡(jiǎn)化:

let person = R.filter(R.where({tags: R.includes('fun')}))

很明顯減少了代碼量且更加容易理解含義。

最后再來看一個(gè)例子:

const oldArr= [[[[[{name: 'yuxiaoliang'}]]]]];

我們想把oldArr這個(gè)多維數(shù)組,最內(nèi)層的那個(gè)name,由小寫轉(zhuǎn)成大寫,用函數(shù)式可以直接這樣寫。

R.map(atem =>       R.map(btem => R.map(ctem => R.map(dtem => R.map(etem => etem.name.toUpperCase())(dtem))(ctem))(btem))(atem),   )(arr);

到此,關(guān)于“如何理解Typescript代碼”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI