<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          TypeScript 高級類型及用法

          共 15237字,需瀏覽 31分鐘

           ·

          2021-11-27 10:42

          來源 | https://github.com/beichensky/Blog/issues/12


          本文詳細介紹了 TypeScript 高級類型的使用場景,對日常 TypeScript 的使用可以提供一些幫助。

          前言

          本文已收錄在 Github: https://github.com/beichensky/Blog 中,走過路過點個 Star 唄。

          一、高級類型

          交叉類型(&)

          交叉類型是將多個類型合并為一個類型。這讓我們可以把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。
          語法:T & U

          其返回類型既要符合 T 類型也要符合 U 類型

          用法:假設有兩個接口:一個是 Ant 螞蟻接口,一個是 Fly 飛翔接口,現(xiàn)在有一只會飛的螞蟻:

          interface Ant {    name: string;    weight: number;}
          interface Fly { flyHeight: number; speed: number;}
          // 少了任何一個屬性都會報錯const flyAnt: Ant & Fly = { name: '螞蟻呀嘿', weight: 0.2, flyHeight: 20, speed: 1,};

          聯(lián)合類型(|)

          聯(lián)合類型與交叉類型很有關聯(lián),但是使用上卻完全不同。

          語法:T | U

          其返回類型為連接的多個類型中的任意一個

          用法:假設聲明一個數(shù)據(jù),既可以是 string 類型,也可以是 number 類型

          let stringOrNumber:string | number = 0
          stringOrNumber = ''

          再看下面這個例子,start 函數(shù)的參數(shù)類型既是 Bird | Fish,那么在 start 函數(shù)中,想要直接調(diào)用的話,只能調(diào)用 Bird 和 Fish 都具備的方法,否則編譯會報錯

          class Bird {    fly() {        console.log('Bird flying');    }    layEggs() {        console.log('Bird layEggs');    }}
          class Fish { swim() { console.log('Fish swimming'); } layEggs() { console.log('Fish layEggs'); }}
          const bird = new Bird();const fish = new Fish();
          function start(pet: Bird | Fish) { // 調(diào)用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法 pet.layEggs();
          // 會報錯:Property 'fly' does not exist on type 'Bird | Fish' // pet.fly();
          // 會報錯:Property 'swim' does not exist on type 'Bird | Fish' // pet.swim();}
          start(bird);
          start(fish);

          二、關鍵字

          類型約束(extends)

          語法:T extends K

          這里的 extends 不是類、接口的繼承,而是對于類型的判斷和約束,意思是判斷 T 能否賦值給 K

          可以在泛型中對傳入的類型進行約束。

          const copy = (value: string | number): string | number => value
          // 只能傳入 string 或者 numbercopy(10)
          // 會報錯:Argument of type 'boolean' is not assignable to parameter of type 'string | number'// copy(false)

          也可以判斷 T 是否可以賦值給 U,可以的話返回 T,否則返回 never

          type Exclude<T, U> = T extends U ? T : never;

          類型映射(in)

          會遍歷指定接口的 key 或者是遍歷聯(lián)合類型

          interface Person {    name: string    age: number    gender: number}
          // 將 T 的所有屬性轉(zhuǎn)換為只讀類型type ReadOnlyType<T> = { readonly [P in keyof T]: T[P]}
          // type ReadOnlyPerson = {// readonly name: Person;// readonly age: Person;// readonly gender: Person;// }type ReadOnlyPerson = ReadOnlyType<Person>

          類型謂詞(is)

          語法:parameterName is Type

          parameterName 必須是來自于當前函數(shù)簽名里的一個參數(shù)名,判斷 parameterName 是否是 Type 類型。

          具體的應用場景可以跟著下面的代碼思路進行使用:

          看完聯(lián)合類型的例子后,可能會考慮:如果想要在 start 函數(shù)中,根據(jù)情況去調(diào)用 Bird 的 fly 方法和 Fish 的 swim 方法,該如何操作呢?

          首先想到的可能是直接檢查成員是否存在,然后進行調(diào)用:

          function start(pet: Bird | Fish) {    // 調(diào)用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法    pet.layEggs();
          if ((pet as Bird).fly) { (pet as Bird).fly(); } else if ((pet as Fish).swim) { (pet as Fish).swim(); }}

          但是這樣做,判斷以及調(diào)用的時候都要進行類型轉(zhuǎn)換,未免有些麻煩,可能會想到寫個工具函數(shù)判斷下:

          function isBird(bird: Bird | Fish): boolean {    return !!(bird as Bird).fly;}
          function isFish(fish: Bird | Fish): boolean { return !!(fish as Fish).swim;}
          function start(pet: Bird | Fish) { // 調(diào)用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法 pet.layEggs();
          if (isBird(pet)) { (pet as Bird).fly(); } else if (isFish(pet)) { (pet as Fish).swim(); }}

          看起來簡潔了一點,但是調(diào)用方法的時候,還是要進行類型轉(zhuǎn)換才可以,否則還是會報錯,那有什么好的辦法,能讓我們判斷完類型之后,就可以直接調(diào)用方法,不用再進行類型轉(zhuǎn)換呢?

          OK,肯定是有的,類型謂詞 is 就派上用場了

          用法:

          function isBird(bird: Bird | Fish): bird is Bird {    return !!(bird as Bird).fly}
          function start(pet: Bird | Fish) { // 調(diào)用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法 pet.layEggs();
          if (isBird(pet)) { pet.fly(); } else { pet.swim(); }};

          每當使用一些變量調(diào)用 isFish 時,TypeScript 會將變量縮減為那個具體的類型,只要這個類型與變量的原始類型是兼容的。

          TypeScript 不僅知道在 if 分支里 pet 是 Fish 類型;它還清楚在 else 分支里,一定不是 Fish 類型,一定是 Bird 類型

          待推斷類型(infer)

          可以用 infer P 來標記一個泛型,表示這個泛型是一個待推斷的類型,并且可以直接使用

          比如下面這個獲取函數(shù)參數(shù)類型的例子:

          type ParamType<T> = T extends (param: infer P) => any ? P : T;
          type FunctionType = (value: number) => boolean
          type Param = ParamType<FunctionType>; // type Param = number
          type OtherParam = ParamType<symbol>; // type Param = symbol

          判斷 T 是否能賦值給 (param: infer P) => any,并且將參數(shù)推斷為泛型 P,如果可以賦值,則返回參數(shù)類型 P,否則返回傳入的類型

          再來一個獲取函數(shù)返回類型的例子:

          type ReturnValueType<T> = T extends (param: any) => infer U ? U : T;
          type FunctionType = (value: number) => boolean
          type Return = ReturnValueType<FunctionType>; // type Return = boolean
          type OtherReturn = ReturnValueType<number>; // type OtherReturn = number

          判斷 T 是否能賦值給 (param: any) => infer U,并且將返回值類型推斷為泛型 U,如果可以賦值,則返回返回值類型 P,否則返回傳入的類型

          原始類型保護(typeof)

          語法:typeof v === "typename" 或 typeof v !== "typename"

          用來判斷數(shù)據(jù)的類型是否是某個原始類型(number、string、boolean、symbol)并進行類型保護

          "typename"必須是 "number", "string", "boolean"或 "symbol"。但是 TypeScript 并不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護。

          看下面這個例子, print 函數(shù)會根據(jù)參數(shù)類型打印不同的結(jié)果,那如何判斷參數(shù)是 string 還是 number 呢?

          function print(value: number | string) {    // 如果是 string 類型    // console.log(value.split('').join(', '))
          // 如果是 number 類型 // console.log(value.toFixed(2))}

          有兩種常用的判斷方式:

          根據(jù)是否包含 split 屬性判斷是 string 類型,是否包含 toFixed 方法判斷是 number 類型

          弊端:不論是判斷還是調(diào)用都要進行類型轉(zhuǎn)換

          使用類型謂詞 is

          弊端:每次都要去寫一個工具函數(shù),太麻煩了

          用法:這就到了 typeof 一展身手的時候了

          function print(value: number | string) {    if (typeof value === 'string') {        console.log(value.split('').join(', '))    } else {        console.log(value.toFixed(2))    }}

          使用 typeof 進行類型判斷后,TypeScript 會將變量縮減為那個具體的類型,只要這個類型與變量的原始類型是兼容的。

          類型保護(instanceof)

          與 typeof 類似,不過作用方式不同,instanceof 類型保護是通過構(gòu)造函數(shù)來細化類型的一種方式。

          instanceof 的右側(cè)要求是一個構(gòu)造函數(shù),TypeScript 將細化為:

          此構(gòu)造函數(shù)的 prototype 屬性的類型,如果它的類型不為 any 的話

          構(gòu)造簽名所返回的類型的聯(lián)合

          還是以 類型謂詞 is 示例中的代碼做演示:

          最初代碼:

          function start(pet: Bird | Fish) {    // 調(diào)用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法    pet.layEggs();
          if ((pet as Bird).fly) { (pet as Bird).fly(); } else if ((pet as Fish).swim) { (pet as Fish).swim(); }}

          使用 instanceof 后的代碼:

          function start(pet: Bird | Fish) {    // 調(diào)用 layEggs 沒問題,因為 Bird 或者 Fish 都有 layEggs 方法    pet.layEggs();
          if (pet instanceof Bird) { pet.fly(); } else { pet.swim(); }}

          可以達到相同的效果

          索引類型查詢操作符(keyof)

          語法:keyof T

          對于任何類型 T, keyof T 的結(jié)果為 T 上已知的 公共屬性名 的 聯(lián)合

          interface Person {    name: string;    age: number;}
          type PersonProps = keyof Person; // 'name' | 'age'

          這里,keyof Person 返回的類型和 'name' | 'age' 聯(lián)合類型是一樣,完全可以互相替換

          用法:keyof 只能返回類型上已知的 公共屬性名

          class Animal {    type: string;    weight: number;    private speed: number;}
          type AnimalProps = keyof Animal; // "type" | "weight"

          例如我們經(jīng)常會獲取對象的某個屬性值,但是不確定是哪個屬性,這個時候可以使用 extends 配合 typeof 對屬性名進行限制,限制傳入的參數(shù)只能是對象的屬性名

          const person = {    name: 'Jack',    age: 20}
          function getPersonValue<T extends keyof typeof person>(fieldName: keyof typeof person) { return person[fieldName]}
          const nameValue = getPersonValue('name')const ageValue = getPersonValue('age')
          // 會報錯:Argument of type '"gender"' is not assignable to parameter of type '"name" | "age"'// getPersonValue('gender')

          索引訪問操作符(T[K])

          語法:T[K]

          類似于 js 中使用對象索引的方式,只不過 js 中是返回對象屬性的值,而在 ts 中返回的是 T 對應屬性 P 的類型

          用法:

          interface Person {    name: string    age: number    weight: number | string    gender: 'man' | 'women'}
          type NameType = Person['name'] // string
          type WeightType = Person['weight'] // string | number
          type GenderType = Person['gender'] // "man" | "women"

          三、映射類型

          只讀類型(Readonly<T>)

          定義:

          type Readonly<T> = {    readonly [P in keyof T]: T[P];}

          用于將 T 類型的所有屬性設置為只讀狀態(tài)。

          用法:

          interface Person {    name: string    age: number}
          const person: Readonly<Person> = { name: 'Lucy', age: 22}
          // 會報錯:Cannot assign to 'name' because it is a read-only propertyperson.name = 'Lily'

          readonly 只讀, 被 readonly 標記的屬性只能在聲明時或類的構(gòu)造函數(shù)中賦值,之后將不可改(即只讀屬性)

          只讀數(shù)組(ReadonlyArray<T>)

          定義:

          interface ReadonlyArray<T> {    /** Iterator of values in the array. */    [Symbol.iterator](): IterableIterator<T>;
          /** * Returns an iterable of key, value pairs for every entry in the array */ entries(): IterableIterator<[number, T]>;
          /** * Returns an iterable of keys in the array */ keys(): IterableIterator<number>;
          /** * Returns an iterable of values in the array */ values(): IterableIterator<T>;}

          只能在數(shù)組初始化時為變量賦值,之后數(shù)組無法修改

          使用:

          interface Person {    name: string}
          const personList: ReadonlyArray<Person> = [{ name: 'Jack' }, { name: 'Rose' }]
          // 會報錯:Property 'push' does not exist on type 'readonly Person[]'// personList.push({ name: 'Lucy' })
          // 但是內(nèi)部元素如果是引用類型,元素自身是可以進行修改的personList[0].name = 'Lily'

          可選類型(Partial<T>)

          用于將 T 類型的所有屬性設置為可選狀態(tài),首先通過 keyof T,取出類型 T 的所有屬性,
          然后通過 in 操作符進行遍歷,最后在屬性后加上 ?,將屬性變?yōu)榭蛇x屬性。

          定義:

          type Partial<T> = {    [P in keyof T]?: T[P];}

          用法:

          interface Person {    name: string    age: number}
          // 會報錯:Type '{}' is missing the following properties from type 'Person': name, age// let person: Person = {}
          // 使用 Partial 映射后返回的新類型,name 和 age 都變成了可選屬性let person: Partial<Person> = {}
          person = { name: 'pengzu', age: 800 }
          person = { name: 'z' }
          person = { age: 18 }

          必選類型(Required<T>)

          和 Partial 的作用相反

          用于將 T 類型的所有屬性設置為必選狀態(tài),首先通過 keyof T,取出類型 T 的所有屬性,
          然后通過 in 操作符進行遍歷,最后在屬性后的 ? 前加上 -,將屬性變?yōu)楸剡x屬性。

          定義:

          type Required<T> = {    [P in keyof T]-?: T[P];}

          使用:

          interface Person {    name?: string    age?: number}
          // 使用 Required 映射后返回的新類型,name 和 age 都變成了必選屬性// 會報錯:Type '{}' is missing the following properties from type 'Required<Person>': name, agelet person: Required<Person> = {}

          提取屬性(Pick<T>)

          定義:

          type Pick<T, K extends keyof T> = {    [P in K]: T[P];}

          從 T 類型中提取部分屬性,作為新的返回類型。

          使用:比如我們在發(fā)送網(wǎng)絡請求時,只需要傳遞類型中的部分屬性,就可以通過 Pick 來實現(xiàn)。

          interface Goods {    type: string    goodsName: string    price: number}
          // 作為網(wǎng)絡請求參數(shù),只需要 goodsName 和 price 就可以type RequestGoodsParams = Pick<Goods, 'goodsName' | 'price'>// 返回類型:// type RequestGoodsParams = {// goodsName: string;// price: number;// }const params: RequestGoodsParams = { goodsName: '', price: 10}

          排除屬性(Omit<T>)

          定義:type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

          和 Pick 作用相反,用于從 T 類型中,排除部分屬性

          用法:比如長方體有長寬高,而正方體長寬高相等,所以只需要長就可以,那么此時就可以用 Omit 來生成正方體的類型。

          interface Rectangular {    length: number    height: number    width: number}
          type Square = Omit<Rectangular, 'height' | 'width'>// 返回類型:// type Square = {// length: number;// }
          const temp: Square = { length: 5 }

          摘取類型(Extract<T, U>)

          語法:Extract<T, U>

          提取 T 中可以 賦值 給 U 的類型

          定義:type Extract<T, U> = T extends U ? T : never;

          用法:

          type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"
          type T02 = Extract<string | number | (() => void), Function>; // () => void

          排除類型(Exclude<T, U>)

          語法:Exclude<T, U>

          與 Extract 用法相反,從 T 中剔除可以賦值給 U的類型

          定義:type Exclude<T, U> = T extends U ? never : T

          用法:

          type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
          type T01 = Exclude<string | number | (() => void), Function>; // string | number

          屬性映射(Record<K, T>)

          定義:

          type Record<K extends string | number | symbol, T> = {    [P in K]: T;}

          接收兩個泛型,K 必須可以是可以賦值給 string | number | symbol 的類型,通過 in 操作符對 K 進行遍歷,每一個屬性的類型都必須是 T 類型

          用法:比如我們想要將 Person 類型的數(shù)組轉(zhuǎn)化成對象映射,可以使用 Record 來指定映射對象的類型。

          interface Person {    name: string    age: number}
          const personList = [ { name: 'Jack', age: 26 }, { name: 'Lucy', age: 22 }, { name: 'Rose', age: 18 },]
          const personMap: Record<string, Person> = {}
          personList.map((person) => { personMap[person.name] = person})

          比如在傳遞參數(shù)時,希望參數(shù)是一個對象,但是不確定具體的類型,就可以使用 Record 作為參數(shù)類型。

          function doSomething(obj: Record<string, any>) {}

          不可為空類型(NonNullable<T>)

          定義:type NonNullable<T> = T extends null | undefined ? never : T

          從 T 中剔除 null、undefined、never 類型,不會剔除 void、unknow 類型

          type T01 = NonNullable<string | number | undefined>;  // string | number
          type T02 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]
          type T03 = NonNullable<{name?: string, age: number} | string[] | null | undefined>; // {name?: string, age: number} | string[]

          構(gòu)造函數(shù)參數(shù)類型(ConstructorParameters<typeof T>)

          返回 class 中構(gòu)造函數(shù)參數(shù)類型組成的 元組類型

          定義:

          /** * Obtain the parameters of a constructor function type in a tuple */type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

          使用:

          class Person {    name: string    age: number    weight: number    gender: 'man' | 'women'
          constructor(name: string, age: number, gender: 'man' | 'women') { this.name = name this.age = age; this.gender = gender }}
          type ConstructorType = ConstructorParameters<typeof Person> // [name: string, age: number, gender: "man" | "women"]
          const params: ConstructorType = ['Jack', 20, 'man']

          實例類型(InstanceType<T>)

          獲取 class 構(gòu)造函數(shù)的返回類型

          定義:

          /** * Obtain the return type of a constructor function type */type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

          使用:

          class Person {    name: string    age: number    weight: number    gender: 'man' | 'women'
          constructor(name: string, age: number, gender: 'man' | 'women') { this.name = name this.age = age; this.gender = gender }}
          type Instance = InstanceType<typeof Person> // Person
          const params: Instance = { name: 'Jack', age: 20, weight: 120, gender: 'man'}

          函數(shù)參數(shù)類型(Parameters<T>)

          獲取函數(shù)的參數(shù)類型組成的 元組

          定義:

          /** * Obtain the parameters of a function type in a tuple */type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

          用法:

          type FunctionType = (name: string, age: number) => boolean
          type FunctionParamsType = Parameters<FunctionType> // [name: string, age: number]
          const params: FunctionParamsType = ['Jack', 20]

          函數(shù)返回值類型(ReturnType<T>)

          獲取函數(shù)的返回值類型

          定義:

          /** * Obtain the return type of a function type */type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

          使用:

          type FunctionType = (name: string, age: number) => boolean | string
          type FunctionReturnType = ReturnType<FunctionType> // boolean | string

          四、總結(jié)

          高級類型

          用法描述
          &交叉類型,將多個類型合并為一個類型,交集
          |聯(lián)合類型,將多個類型組合成一個類型,可以是多個類型的任意一個,并集
          關鍵字
          用法描述
          T extends U類型約束,判斷 T 是否可以賦值給 U
          P in T類型映射,遍歷 T 的所有類型
          parameterName is Type類型謂詞,判斷函數(shù)參數(shù) parameterName 是否是 Type 類型
          infer P待推斷類型,使用 infer 標記類型 P,就可以使用待推斷的類型 P
          typeof v === "typename"原始類型保護,判斷數(shù)據(jù)的類型是否是某個原始類型(number、string、boolean、symbol)
          instanceof v類型保護,判斷數(shù)據(jù)的類型是否是構(gòu)造函數(shù)的 prototype 屬性類型
          keyof索引類型查詢操作符,返回類型上已知的 公共屬性名
          T[K]索引訪問操作符,返回 T 對應屬性 P 的類型
          映射類型
          用法描述
          Readonly將 T 中所有屬性都變?yōu)橹蛔x
          ReadonlyArray返回一個 T 類型的只讀數(shù)組
          ReadonlyMap<T, U>返回一個 T 和 U 類型組成的只讀 Map
          Partial將 T 中所有的屬性都變成可選類型
          Required將 T 中所有的屬性都變成必選類型
          Pick<T, K extends keyof T>從 T 中摘取部分屬性
          Omit<T, K extends keyof T>從 T 中排除部分屬性
          Exclude<T, U>從 T 中剔除可以賦值給 U 的類型
          Extract<T, U>提取 T 中可以賦值給 U 的類型
          Record<K, T>返回屬性名為 K,屬性值為 T 的類型
          NonNullable從 T 中剔除 null 和 undefined
          ConstructorParameters獲取 T 的構(gòu)造函數(shù)參數(shù)類型組成的元組
          InstanceType獲取 T 的實例類型
          Parameters獲取函數(shù)參數(shù)類型組成的元組
          ReturnType獲取函數(shù)返回值類型

          本文完~


          學習更多技能

          請點擊下方公眾號

          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  水蜜桃视频在线观看 | 午夜人妖另类 | 九九视频中文字幕 | 色吧大香蕉 | 日韩人妻AV |