TypeScript 強大的類型別名

TS 有個很是好用的功能就是類型別名。javascript

類型別名會給一個類型起個新名字。類型別名有時和接口很像,可是能夠做用於原始值,聯合類型,元組以及其它任何你須要手寫的類型。html

一些關鍵字

使用類型別名能夠實現不少複雜的類型,不少複雜的類型別名都須要藉助關鍵字,咱們先來了解一下幾個經常使用的關鍵字:java

extends

extends 能夠用來繼承一個類,也能夠用來繼承一個 interface,但還能夠用來判斷有條件類型:node

T extends U ? X : Y;
複製代碼

上面的類型意思是,若 T 可以賦值給 U,那麼類型是 X,不然爲 Ygit

原理是令 T'U' 分別爲 TU 的實例,並將全部類型參數替換爲 any,若是 T' 能賦值給 U',則將有條件的類型解析成 X,不然爲Ygithub

上面的官方解釋有點繞,下面舉個栗子:typescript

type Words = 'a'|'b'|"c";

type W<T> = T extends Words ? true : false;

type WA = W<'a'>; // -> true
type WD = W<'d'>; // -> false
複製代碼

a 能夠賦值給 Words 類型,因此 WAtrue,而 d 不能賦值給 Words 類型,因此 WDfalse數組

typeof

在 JS 中 typeof 能夠判斷一個變量的基礎數據類型,在 TS 中,它還有一個做用,就是獲取一個變量的聲明類型,若是不存在,則獲取該類型的推論類型。函數

舉兩個栗子:工具

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

const jack: Person = { name: 'jack', age: 100 };
type Jack = typeof jack; // -> Person

function foo(x: number): Array<number> {
  return [x];
}

type F = typeof foo; // -> (x: number) => number[]
複製代碼

Jack 這個類型別名實際上就是 jack 的類型 Person,而 F 的類型就是 TS 本身推導出來的 foo 的類型 (x: number) => number[]

keyof

keyof 能夠用來取得一個對象接口的全部 key 值:

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

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number
複製代碼

in

in 能夠遍歷枚舉類型:

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

上面 in 遍歷 Keys,併爲每一個值賦予 any 類型。

infer

在條件類型語句中, 能夠用 infer 聲明一個類型變量而且對它進行使用,

咱們能夠用它獲取函數的返回類型, 源碼以下:

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R
  ? R
  : any;
複製代碼

其實這裏的 infer R 就是聲明一個變量來承載傳入函數簽名的返回值類型, 簡單說就是用它取到函數返回值的類型方便以後使用。

內置類型別名

下面咱們看一下 TS 內置的一些類型別名:

Partial

Partial 的做用就是能夠將某個類型裏的屬性所有變爲可選項 ?

源碼:

// node_modules/typescript/lib/lib.es5.d.ts

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

從源碼能夠看到 keyof T 拿到 T 全部屬性名, 而後 in 進行遍歷, 將值賦給 P, 最後 T[P] 取得相應屬性的值. 結合中間的 ?,將全部屬性變爲可選.

Required

Required 的做用恰好跟 Partial 相反,Partial 是將全部屬性改爲可選項,Required 則是將全部類型改爲必選項,源碼以下:

// node_modules/typescript/lib/lib.es5.d.ts

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

其中 -? 是表明移除 ? 這個 modifier 的標識。

與之對應的還有個 +? , 這個含義天然與 -? 以前相反, 它是用來把屬性變成可選項的,+ 可省略,見 Partial

再拓展一下,除了能夠應用於 ? 這個 modifiers ,還有應用在 readonly ,好比 Readonly.

Readonly

這個類型的做用是將傳入的屬性變爲只讀選項。

// node_modules/typescript/lib/lib.es5.d.ts

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

給子屬性添加 readonly 的標識,若是將上面的 readonly 改爲 -readonly, 就是移除子屬性的 readonly 標識。

Pick

這個類型則能夠將某個類型中的子屬性挑出來,變成包含這個類型部分屬性的子類型。

源碼實現以下:

// node_modules/typescript/lib/lib.es5.d.ts

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

從源碼能夠看到 K 必須是 T 的 key,而後用 in 進行遍歷, 將值賦給 P, 最後 T[P] 取得相應屬性的值。

Record

該類型能夠將 K 中全部的屬性的值轉化爲 T 類型,源碼實現以下:

// node_modules/typescript/lib/lib.es5.d.ts

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

能夠根據 K 中的全部可能值來設置 key,以及 value 的類型,舉個例子:

type T11 = Record<'a' | 'b' | 'c', Person>; // -> { a: Person; b: Person; c: Person; }
複製代碼

Exclude

Exclude 將某個類型中屬於另外一個的類型移除掉。

源碼的實現:

// node_modules/typescript/lib/lib.es5.d.ts

type Exclude<T, U> = T extends U ? never : T;
複製代碼

以上語句的意思就是 若是 T 能賦值給 U 類型的話,那麼就會返回 never 類型,不然返回 T,最終結果是將 T 中的某些屬於 U 的類型移除掉,舉個例子:

type T00 = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>;  // -> 'b' | 'd'
複製代碼

能夠看到 T'a' | 'b' | 'c' | 'd' ,而後 U'a' | 'c' | 'f' ,返回的新類型就能夠將 U 中的類型給移除掉,也就是 'b' | 'd' 了。

Extract

Extract 的做用是提取出 T 包含在 U 中的元素,換種更加貼近語義的說法就是從 T 中提取出 U,源碼以下:

// node_modules/typescript/lib/lib.es5.d.ts

type Extract<T, U> = T extends U ? T : never;
複製代碼

以上語句的意思就是 若是 T 能賦值給 U 類型的話,那麼就會返回 T 類型,不然返回 never,最終結果是將 TU 中共有的屬性提取出來,舉個例子:

type T01 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>;  // -> 'a' | 'c'
複製代碼

能夠看到 T'a' | 'b' | 'c' | 'd' ,而後 U'a' | 'c' | 'f' ,返回的新類型就能夠將 TU 中共有的屬性提取出來,也就是 'a' | 'c' 了。

ReturnType

該類型的做用是獲取函數的返回類型。

源碼的實現

// node_modules/typescript/lib/lib.es5.d.ts

type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
複製代碼

實際使用的話,就能夠經過 ReturnType 拿到函數的返回類型,以下的示例:

function foo(x: number): Array<number> {
  return [x];
}

type fn = ReturnType<typeof foo>; // -> number[]
複製代碼

ThisType

這個類型是用於指定上下文對象類型的。

// node_modules/typescript/lib/lib.es5.d.ts

interface ThisType<T> { }
複製代碼

能夠看到聲明中只有一個接口,沒有任何的實現,說明這個類型是在 TS 源碼層面支持的,而不是經過類型變換。

這類型怎麼用呢,舉個例子:

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

const obj: ThisType<Person> = {
  dosth() {
    this.name // string
  }
}
複製代碼

這樣的話,就能夠指定 obj 裏的全部方法裏的上下文對象改爲 Person 這個類型了。

InstanceType

該類型的做用是獲取構造函數類型的實例類型。

源碼實現:

// node_modules/typescript/lib/lib.es5.d.ts

type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
複製代碼

看一下官方的例子:

class C {
    x = 0;
    y = 0;
}

type T20 = InstanceType<typeof C>;  // C
type T21 = InstanceType<any>;  // any
type T22 = InstanceType<never>;  // any
type T23 = InstanceType<string>;  // Error
type T24 = InstanceType<Function>;  // Error
複製代碼

NonNullable

這個類型能夠用來過濾類型中的 nullundefined 類型。

源碼實現:

// node_modules/typescript/lib/lib.es5.d.ts

type NonNullable<T> = T extends null | undefined ? never : T;
複製代碼

好比:

type T22 = string | number | null;
type T23 = NonNullable<T22>; // -> string | number;
複製代碼

Parameters

該類型能夠得到函數的參數類型組成的元組類型。

源碼實現:

// node_modules/typescript/lib/lib.es5.d.ts

type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;
複製代碼

舉個栗子:

function foo(x: number): Array<number> {
  return [x];
}

type P = Parameters<typeof foo>; // -> [number]
複製代碼

此時 P 的真實類型就是 foo 的參數組成的元組類型 [number]

ConstructorParameters

該類型的做用是得到類的參數類型組成的元組類型,源碼:

// node_modules/typescript/lib/lib.es5.d.ts

type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never;
複製代碼

舉個栗子:

class Person {
  private firstName: string;
  private lastName: string;
  
  constructor(firstName: string, lastName: string) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

type P = ConstructorParameters<typeof Person>; // -> [string, string]
複製代碼

此時 P 就是 Personconstructor 的參數 firstNamelastName 的類型所組成的元組類型 [string, string]

自定義類型別名

下面是一些可能會常常用到,可是 TS 沒有內置的一些類型別名:

Omit

有時候咱們想要繼承某個接口,可是又須要在新接口中將某個屬性給 overwrite 掉,這時候經過 PickExclude 就能夠組合出來 Omit,用來忽略對象某些屬性功能:

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

// 使用
type Foo = Omit<{name: string, age: number}, 'name'> // -> { age: number }
複製代碼

Mutable

將 T 的全部屬性的 readonly 移除:

type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}
複製代碼

PowerPartial

內置的 Partial 有個侷限性,就是隻支持處理第一層的屬性,若是是嵌套多層的就沒有效果了,不過能夠以下自定義:

type PowerPartial<T> = {
    // 若是是 object,則遞歸類型
    [U in keyof T]?: T[U] extends object
      ? PowerPartial<T[U]>
      : T[U]
};
複製代碼

Deferred

相同的屬性名稱,但使值是一個 Promise,而不是一個具體的值:

type Deferred<T> = {
    [P in keyof T]: Promise<T[P]>;
};
複製代碼

Proxify

T 的屬性添加代理

type Proxify<T> = {
    [P in keyof T]: { get(): T[P]; set(v: T[P]): void }
};
複製代碼

若有疑問,歡迎斧正!

參考

TypeScript中文網

TS 中的內置類型簡述

TypeScript 一些你可能不知道的工具泛型的使用及其實現

相關文章
相關標籤/搜索