切換語言為:簡體
TypeScript 內建高階型別詳細介紹

TypeScript 內建高階型別詳細介紹

  • 爱糖宝
  • 2024-11-25
  • 2003
  • 0
  • 0

Parameters

Parameters 用於提取函式型別的引數型別。

原始碼是這樣的:

type Parameters<T extends (...args: any) => any> 
    = T extends (...args: infer P) => any 
        ? P 
        : never;


1. 引數說明

  • T:要從中提取引數型別的函式型別。

2. 條件型別 T extends (...args: infer P) => any

  • 這個條件型別用於檢查 T 是否是一個函式型別。

  • (...args: infer P) => any 是一個函式簽名,其中 ...args: infer P 表示函式可以接受任意數量和型別的引數,infer P 用於推斷函式的引數型別。

3. 分支 ? P : never

  • 如果 T 是一個函式型別,並且成功推斷出引數型別 P,則返回 P

  • 如果 T 不是一個函式型別,或者無法推斷出引數型別,則返回 never

型別引數 T 為待處理的型別,透過 extends 約束為函式,引數和返回值任意。

透過 extends 匹配一個模式型別,提取引數的型別到 infer 宣告的區域性變數 P 中返回。

這樣就實現了函式引數型別的提取:

function add(a: number, b: number): number {
        return a + b; 
        } 
type AddParameters = Parameters<typeof add>; // [number, number]


ReturnType

ReturnType 用於提取函式型別的返回值型別。

原始碼是這樣的:

type ReturnType<T extends (...args: any) => any> 
    = T extends (...args: any) => infer R 
        ? R 
        : any;


1. 引數說明

  • T:要從中提取返回型別的函式型別。

2. 條件型別 T extends (...args: any) => infer R

  • 這個條件型別用於檢查 T 是否是一個函式型別。

  • (...args: any) => infer R 是一個函式簽名,其中 ...args: any 表示函式可以接受任意數量和型別的引數,infer R 用於推斷函式的返回型別。

3. 分支 ? R : any

  • 如果 T 是一個函式型別,並且成功推斷出返回型別 R,則返回 R

  • 如果 T 不是一個函式型別,或者無法推斷出返回型別,則返回 any

型別引數 T 為待處理的型別,透過 extends 約束為函式型別,引數和返回值任意。

用 T 匹配一個模式型別,提取返回值的型別到 infer 宣告的區域性變數 R 裡返回。

這樣就實現了函式返回值型別的提取:

function add(a: number, b: number): number {
    return a + b;
}

type AddReturnType = ReturnType<typeof add>; // number


  • typeof add 是 function (a: number, b: number): number

  • ReturnType<typeof add> 成功推斷出返回型別 number

Partial

索引型別可以透過對映型別的語法做修改,比如把索引變為可選。

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


1. 引數說明

  • T:要處理的物件型別。

2. 對映型別 [P in keyof T]

  • keyof T 獲取 T 型別的所有鍵的聯合型別。

  • [P in keyof T] 是一個對映型別,遍歷 T 的所有鍵 P

3. 可選屬性 ?

  • T[P]? 表示將 T 的每個屬性 P 變為可選屬性。

型別引數 T 為待處理的型別。

透過對映型別的語法構造一個新的索引型別返回,索引 P 是來源於之前的 T 型別的索引,也就是 P in keyof T,索引值的型別也是之前的,也就是 T[P]。

這樣就實現了把索引型別的索引變為可選的效果:

interface User {
    id: number;
    profile: {
        name: string;
        email: string;
    };
}

type PartialUser = Partial<User>;


  • User 型別的屬性 id 和 profile 都是必需的。

  • Partial<User> 將這些屬性變為可選的,因此 PartialUser 的型別是:

    {
        id?: number;
        profile?: {
            name: string;
            email: string;
        };
    }


    Required

可以把索引變為可選,也同樣可以去掉可選,也就是 Required 型別:

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


1. 引數說明

  • T:要處理的物件型別。

2. 對映型別 [P in keyof T]-?

  • keyof T 獲取 T 型別的所有鍵的聯合型別。

  • [P in keyof T]-? 是一個對映型別,遍歷 T 的所有鍵 P,並將每個屬性 P 的可選性移除(即變為必需)。

3. 必需屬性 -?

  • T[P]-? 表示將 T 的每個屬性 P 的可選性移除,使其成為必需屬性。

型別引數 T 為待處理的型別。

透過對映型別的語法構造一個新的索引型別,索引取自之前的索引,也就是 P in keyof T,但是要去掉可選,也就是 -?,值的型別也是之前的,就是 T[P]。

這樣就實現了去掉可選修飾的目的:

interface Person {
    name?: string;
    age?: number;
    address: string;
}

type RequiredPerson = Required<Person>;


  • Person 型別的屬性 name 和 age 是可選的,而 address 是必需的。

  • Required<Person> 將所有屬性變為必需的,因此 RequiredPerson 的型別是:

    {
        name: string;
        age: number;
        address: string;
    }


    Readonly

同樣的方式,也可以新增 readonly 的修飾:

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


型別引數 T 為待處理的型別。

透過對映型別的語法構造一個新的索引型別返回,索引和值的型別都是之前的,也就是 P in keyof T 和 T[P],但是要加上 readonly 的修飾。

Pick

對映型別的語法用於構造新的索引型別,在構造的過程中可以對索引和值做一些修改或過濾。

比如可以用 Pick 實現過濾:

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


1. 引數說明

  • T:要從中選擇屬性的物件型別。

  • K:要選擇的屬性鍵的聯合型別,必須是 T 的鍵的子集(即 K 必須是 keyof T 的一部分)。

2. 對映型別 [P in K]

  • keyof T 獲取 T 型別的所有鍵的聯合型別。

  • [P in K] 是一個對映型別,遍歷 K 中的所有鍵 P

3. 屬性對映 T[P]

  • T[P] 表示從 T 型別中選擇鍵 P 對應的屬性型別。

型別引數 T 為待處理的型別,型別引數 K 為要過濾出的索引,透過 extends 約束為只能是 T 的索引的子集。

構造新的索引型別返回,索引取自 K,也就是 P in K,值則是它對應的原來的值,也就是 T[P]。

這樣就實現了過濾的目的:

interface Person {
    name: string;
    age: number;
    address: string;
}

type PersonNameAndAge = Pick<Person, 'name' | 'age'>;


  • Person 型別的屬性 nameage 和 address 都是必需的。

  • Pick<Person, 'name' | 'age'> 選擇 name 和 age 屬性,因此 PersonNameAndAge 的型別是:

    {
        name: string;
        age: number;
    }


Record

Record 用於建立索引型別,傳入 key 和值的型別:

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


1. 引數說明

  • K:鍵的聯合型別,必須是 keyof any 的子集,即 K 必須是有效的鍵名。

  • T:每個鍵對應的值型別。

2. 對映型別 [P in K]

  • [P in K] 是一個對映型別,遍歷 K 中的所有鍵 P

3. 屬性對映 T

  • T 表示每個鍵 P 對應的值型別。

type UserStatus = 'active' | 'inactive' | 'pending';

type UserStatusCount = Record<UserStatus, number>;

const userStatusCount: UserStatusCount = {
    active: 10,
    inactive: 5,
    pending: 3
};


  • UserStatusCount 型別的鍵是 'active''inactive' 和 'pending',每個鍵的值型別是 number

  • 因此,userStatusCount 變數必須包含這三個鍵,並且每個鍵的值必須是數字。

Exclude

當想從一個聯合型別中去掉一部分型別時,可以用 Exclude 型別:

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


1. 引數說明

  • T:源型別,從中排除某些部分。

  • U:要排除的型別。

2. 條件型別 T extends U ? never : T

  • 這個條件型別用於檢查 T 是否是 U 的子型別。

  • 如果 T 是 U 的子型別,則返回 never,表示從 T 中排除這部分。

  • 如果 T 不是 U 的子型別,則返回 T,表示保留這部分。

聯合型別當作為型別引數出現在條件型別左邊時,會被分散成單個型別傳入,這叫做分散式條件型別。

所以寫法上可以簡化, T extends U 就是對每個型別的判斷。

過濾掉 U 型別,剩下的型別組成聯合型別。也就是取差集。

type AllTypes = string | number | boolean | null | undefined;
type ExcludeNullish = Exclude<AllTypes, null | undefined>;

// ExcludeNullish 的型別是 string | number | boolean


  • AllTypes 是一個聯合型別,包含 stringnumberbooleannull 和 undefined

  • Exclude<AllTypes, null | undefined> 排除了 null 和 undefined,因此 ExcludeNullish 的型別是 string | number | boolean

Extract

可以過濾掉,自然也可以保留,Exclude 反過來就是 Extract,也就是取交集:

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


1. 引數說明

  • T:源型別,從中提取某些部分。

  • U:要提取的型別。

2. 條件型別 T extends U ? T : never

  • 這個條件型別用於檢查 T 是否是 U 的子型別。

  • 如果 T 是 U 的子型別,則返回 T,表示保留這部分。

  • 如果 T 不是 U 的子型別,則返回 never,表示排除這部分。

示例

基本示例

type AllTypes = string | number | boolean | null | undefined;
type ExtractStrings = Extract<AllTypes, string>;

// ExtractStrings 的型別是 string


  • AllTypes 是一個聯合型別,包含 stringnumberbooleannull 和 undefined

  • Extract<AllTypes, string> 提取了 string 型別,因此 ExtractStrings 的型別是 string

Omit

我們知道了 Pick 可以取出索引型別的一部分索引構造成新的索引型別,那反過來就是去掉這部分索引構造成新的索引型別。

可以結合 Exclude 來輕鬆實現:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;


1. 引數說明

  • T:要從中排除屬性的物件型別。

  • K:要排除的屬性鍵的聯合型別,必須是 keyof any 的子集,即 K 必須是有效的鍵名。

2. 組合使用 Pick 和 Exclude

  • keyof T 獲取 T 型別的所有鍵的聯合型別。

  • Exclude<keyof T, K> 從 keyof T 中排除 K 中的鍵。

  • Pick<T, Exclude<keyof T, K>> 從 T 型別中選擇排除後的鍵。

型別引數 T 為待處理的型別,型別引數 K 為索引允許的型別(string | number | symbol 或者 string)。

透過 Pick 取出一部分索引構造成新的索引型別,這裏用 Exclude 把 K 對應的索引去掉,把剩下的索引保留。

這樣就實現了刪除一部分索引的目的:

interface Person {
    name: string;
    age: number;
    address: string;
    email: string;
}

type PersonWithoutEmail = Omit<Person, 'email'>;

const person: PersonWithoutEmail = {
    name: 'John Doe',
    age: 30,
    address: '123 Main St'
};


  • Person 型別的屬性 nameageaddress 和 email 都是必需的。

  • Omit<Person, 'email'> 排除了 email 屬性,因此 PersonWithoutEmail 的型別是:

    {
        name: string;
        age: number;
        address: string;
    }


    Awaited

在遞迴那節我們寫過取 Promise 的 ValuType 的高階型別,這個比較常用,ts 也給內建了,就是 Awaited。

它的實現比我們當時寫的完善一些:

type Awaited<T> =
    T extends null | undefined
        ? T 
        : T extends object & { then(onfulfilled: infer F): any }
            ? F extends ((value: infer V, ...args: any) => any)
                ? Awaited<V>
                : never 
            : T;


1. 引數說明

  • T:要從中提取最終值的型別。

2. 條件型別 T extends null | undefined

  • 檢查 T 是否是 null 或 undefined

  • 如果是,則直接返回 T

3. 條件型別 T extends object & { then(onfulfilled: infer F): any }

  • 檢查 T 是否是一個物件,並且具有 then 方法。

  • then 方法的引數型別被推斷為 F

4. 條件型別 F extends ((value: infer V, ...args: any) => any)

  • 檢查 F 是否是一個函式,並且其第一個引數型別被推斷為 V

5. 遞迴呼叫 Awaited<V>

  • 如果 F 是一個函式,並且其第一個引數型別為 V,則遞迴呼叫 Awaited<V>,繼續解析 V

6. 預設返回 T

  • 如果 T 不是 null 或 undefined,且不具有 then 方法,則直接返回 T

型別引數 T 是待處理的型別。

如果 T 是 null 或者 undefined,就返回 T。

如果 T 是物件並且有 then 方法,那就提取 then 的引數,也就是 onfulfilled 函式的型別到 infer 宣告的區域性變數 F。

繼續提取 onfullfilled 函式型別的第一個引數的型別,也就是 Promise 返回的值的型別到 infer 宣告的區域性變數 V。

遞迴的處理提取出來的 V,直到不再滿足上面的條件。

這樣就實現了取出巢狀 Promise 的值的型別的目的:

type Result2 = Awaited<Promise<Promise<number>>>; // number


NonNullable

NonNullable 就是用於判斷是否為非空型別,也就是不是 null 或者 undefined 的型別的,實現比較簡單:

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


1. 引數說明

  • T:要從中排除 null 和 undefined 的型別。

2. 條件型別 T extends null | undefined ? never : T

  • 這個條件型別用於檢查 T 是否是 null 或 undefined

  • 如果 T 是 null 或 undefined,則返回 never,表示從 T 中排除這部分。

  • 如果 T 不是 null 或 undefined,則返回 T,表示保留這部分。

示例

基本示例

type NullableTypes = string | number | null | undefined;
type NonNullableTypes = NonNullable<NullableTypes>;

// NonNullableTypes 的型別是 string | number


  • NullableTypes 是一個聯合型別,包含 stringnumbernull 和 undefined

  • NonNullable<NullableTypes> 排除了 null 和 undefined,因此 NonNullableTypes 的型別是 string | number

Uppercase、Lowercase、Capitalize、Uncapitalize

這四個型別是分別實現大寫、小寫、首字母大寫、去掉首字母大寫的。

它們的原始碼時這樣的:

type Uppercase<S extends string> = intrinsic;

type Lowercase<S extends string> = intrinsic;

type Capitalize<S extends string> = intrinsic;

type Uncapitalize<S extends string> = intrinsic;


啥情況,intrinsic 是啥?

這個 intrinsic 是固有的意思,就像 js 裡面的有的方法列印會顯示 [native code] 一樣。這部分型別不是在 ts 裏實現的,而是編譯過程中由 js 實現的。

TypeScript 內建高階型別詳細介紹

其實就是 ts 編譯器處理到這幾個型別時就直接用 js 給算出來了。

為啥要這樣做呢?

因為快啊,解析型別是要處理 AST 的,效能比較差,用 js 直接給算出來那多快呀。

總結

雖然我們學完 6 個套路,各種型別程式設計邏輯都能寫了,但是常用的型別 TS 已經內建了。

這些內建的高階型別用我們學的套路很容易可以實現。

比如用模式匹配可以實現:Parameters、ReturnType

用模式匹配 + 重新構造可以實現:OmitThisParameter

用重新構造可以實現:Partial、Required、Readonly、Pick、Record

用模式匹配 + 遞迴可以實現: Awaited

用聯合型別在分散式條件型別的特性可以實現: Exclude

此外還有 NonNullable 和四個編譯器內部實現的型別:Uppercase、Lowercase、Capitalize、Uncapitalize。

這些型別也不咋需要記,就算忘記了自己也能很快的實現。重點還是放在 6 個型別程式設計的套路上。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.