TypeScript 工具泛型

發表於2023-09-26

highlight: github

theme: juejin

前言

Typescript 中預設內建了很多工具泛型,透過使用這些工具,可以使得我們定義型別更加靈活,高效。本文將會介紹常用泛型工具的使用技巧,以及對其實現原理進行相應的解析,如果有錯誤的地方,還望指出。

Partial\<T>

作用:將傳入物件型別 T 的屬性變為可選屬性

示例

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

const tom: Partial<Person> = {
  name: "Tom",
};

Partial<Person> 等價於

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

實現原理

  1. 透過關鍵字 keyof 將傳入物件型別的鍵值轉換為聯合型別。
  2. 透過關鍵字 in 遍歷聯合型別,即遍歷物件的鍵值。
  3. 透過型別對映,將物件的屬性轉換為可選屬性
type MyPartial<T> = {
  [P in keyof T]?: T[P];
};

Readonly\<T>

作用:把傳入物件型別 T 屬性變為只讀屬性

示例

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

const tom: Readonly<Person> = {
  name: "Tom",
  age: 18;
};

tom.age = 22 // error

Readonly<Person> 等價於

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

實現原理

Partial類似:

  1. 透過關鍵字 keyof 將傳入物件型別的鍵值轉換為聯合型別。
  2. 透過關鍵字 in 遍歷聯合型別,即遍歷物件的鍵值。
  3. 透過型別對映,將物件的屬性轉換為只讀屬性
type Readonly<T> = {
  [P in keyof T]-?: T[P];
};

Required\<T>

作用:把傳入物件型別 T 屬性變為必填屬性

示例

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

let tom: Required<Person>

tom = {
  name: "Tom",
  age: 18;
};
// ok

tom = {
  name: "Tom",
};
// error

實現原理

Partial類似:

  1. 透過關鍵字 keyof 將傳入物件的鍵值轉換為列舉型別。
  2. 透過關鍵字 in 遍歷列舉型別,即遍歷物件的鍵值。
  3. 透過型別對映,再統一透過 -? 修飾符移除 ? 修飾符,從而轉變為必填狀態
type Required<T> = {
  Required [P in keyof T]: T[P];
};

Record\<K,T>

作用:它用來生成一個屬性名為 K,屬性值型別為 T 的物件型別集合。

示例

// 快速生成一個 Person 物件
type Person = Record<"name" | "country", string>;

const Tom: Person = { name: "Tom", country: "America" };

實現原理:

  1. 透過 K extends keyof anyK 引數進行約束,將其約束為任意型別 any 的鍵值。
  2. 透過 in 對鍵值集合 K 進行遍歷,然後生成型別為 T 的鍵值對集合。
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

Exclude\<T,K>

作用:從型別 T 中排除所有可以賦值給型別 U 的型別。

示例

// 從 "a" | "b" | "c" 中排除掉 "a" 型別
type T1 = Exclude<"a" | "b" | "c", "a">;
// T1 = "b" | "c"

// 從 string | number | boolean 中排除掉 string 型別
type T2 = Exclude<string | number | boolean, string>;
// T2 = number | boolean

實現原理

  1. 透過條件型別T extends U ? never : TT 引數進行判別:

    • 如果 T 可賦值給 U ,那麼返回 never(即排除掉T)。
    • 如果 T 不可賦值給 U ,那麼返回 T
  2. 透過分散式條件型別,如果 T 為聯合型別,則將條件型別的結果分發為聯合型別
type Exclude<T, U> = T extends U ? never : T;

Extract\<T,K>

作用:與 Exclude 相反,從型別 T 中提取所有可以賦值給型別 U 的型別。

示例

// 從 "a" | "b" | "c" 中提取出 "a" 型別
type T1 = Extract<"a" | "b" | "c", "a">;
// T1 = "a"

// 從 string | number | boolean 中提取出 string 型別
type T2 = Extract<string | number | boolean, string>;
// T2 = string

type T3 = Extract<string | (() => void), Function>;
// 相當於 type T3 = () => void;

實現原理

Exclude 類似:

  1. 透過條件型別T extends U ? never : TT 引數進行判別:

    • 如果 T 可賦值給 U ,那麼返回 T
    • 如果 T 不可賦值給 U ,那麼返回 never(即排除掉T)。
  2. 透過分散式條件型別,如果 T 為聯合型別,則將條件型別的結果分發為聯合型別
type Extract<T, U> = T extends U ? T : never;

Pick\<T,K>

作用:在 T 中,摘選出 K 屬性。

示例

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

// 從 Person 中摘選出 name 屬性
type PickPerson = Pick<Person, "name">;

const tom: PickPerson = {
  name: "Tom",
};

實現原理

  1. 透過 K extends keyof TK 引數進行約束,將其約束為 T 的鍵值範圍內。
  2. 透過 in 對鍵值集合 K 進行遍歷,然後生成型別為 T 的鍵值對集合。
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

Omit\<T,K>

作用:在 T 中,剔除掉 K 屬性。

示例

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

// 從 Person 中剔除掉 name 屬性
type OmitPerson = Omit<Person, "name">;

const tom: OmitPerson = {
  age: 18,
};

實現原理

  1. 透過 K extends keyof TK 引數進行約束,將其約束為 T 的鍵值範圍內。
  2. 透過 Exclude<keyof T, K> 將型別集合 T 中的 K 型別排除掉。
  3. 透過 Pick<T,Exclude<keyof T, K>>T 中摘選出排除掉 KT 的屬性。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

ReturnType\<T>

作用:獲取函式的返回值型別。

示例

type Fun = () => string;

// 獲取 Fun 返回值的型別
type T1 = ReturnType<Fun>; // T1 = string

type T2 = ReturnType<() => { x: number; y: number }>;
// T2 = { x: number, y: number }

實現原理

  1. 透過 extendsT 引數進行約束,(...args: any) => any 表示一個函式型別,即 T 引數的型別必須是一個函式型別
  2. T extends U ? X : Y 是條件型別(注意和之前表示約束的 extends 做區分),其中 T 是泛型引數,U條件部分X 是符合條件的返回結果,Y 是不符合條件的返回結果。
  3. 推斷型別 infer 的作用是:在條件型別內部宣告一個型別變數(...args: any) => infer R 是條件型別的條件部分,它宣告瞭一個型別變數 R ,用來儲存函式的返回型別。
  4. T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any表示:

    • 如果 T 是函式型別((...args: any) => infer R),則返回 R , 即函式的返回型別。
    • 如果 T 不是函式型別((...args: any) => infer R),則返回 any
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;

參考資料

相關文章