TS 有個很是好用的功能就是類型別名。javascript
類型別名會給一個類型起個新名字。類型別名有時和接口很像,可是能夠做用於原始值,聯合類型,元組以及其它任何你須要手寫的類型。html
使用類型別名能夠實現不少複雜的類型,不少複雜的類型別名都須要藉助關鍵字,咱們先來了解一下幾個經常使用的關鍵字:java
extends
能夠用來繼承一個類,也能夠用來繼承一個 interface
,但還能夠用來判斷有條件類型:node
T extends U ? X : Y;
複製代碼
上面的類型意思是,若 T
可以賦值給 U
,那麼類型是 X
,不然爲 Y
。git
原理是令 T'
和 U'
分別爲 T
和 U
的實例,並將全部類型參數替換爲 any
,若是 T'
能賦值給 U'
,則將有條件的類型解析成 X
,不然爲Y
。github
上面的官方解釋有點繞,下面舉個栗子: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
類型,因此 WA
爲 true
,而 d
不能賦值給 Words
類型,因此 WD
爲 false
。數組
在 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
能夠用來取得一個對象接口的全部 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
能夠遍歷枚舉類型:
type Keys = "a" | "b"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any }
複製代碼
上面 in
遍歷 Keys
,併爲每一個值賦予 any
類型。
在條件類型語句中, 能夠用 infer
聲明一個類型變量而且對它進行使用,
咱們能夠用它獲取函數的返回類型, 源碼以下:
type ReturnType<T> = T extends (
...args: any[]
) => infer R
? R
: any;
複製代碼
其實這裏的 infer R
就是聲明一個變量來承載傳入函數簽名的返回值類型, 簡單說就是用它取到函數返回值的類型方便以後使用。
下面咱們看一下 TS 內置的一些類型別名:
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
的做用恰好跟 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
.
這個類型的做用是將傳入的屬性變爲只讀選項。
// node_modules/typescript/lib/lib.es5.d.ts
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
複製代碼
給子屬性添加 readonly
的標識,若是將上面的 readonly
改爲 -readonly
, 就是移除子屬性的 readonly
標識。
這個類型則能夠將某個類型中的子屬性挑出來,變成包含這個類型部分屬性的子類型。
源碼實現以下:
// 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]
取得相應屬性的值。
該類型能夠將 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
將某個類型中屬於另外一個的類型移除掉。
源碼的實現:
// 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
的做用是提取出 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
,最終結果是將 T
和 U
中共有的屬性提取出來,舉個例子:
type T01 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>; // -> 'a' | 'c'
複製代碼
能夠看到 T
是 'a' | 'b' | 'c' | 'd'
,而後 U
是 'a' | 'c' | 'f'
,返回的新類型就能夠將 T
和 U
中共有的屬性提取出來,也就是 'a' | 'c'
了。
該類型的做用是獲取函數的返回類型。
源碼的實現
// 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[]
複製代碼
這個類型是用於指定上下文對象類型的。
// 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
這個類型了。
該類型的做用是獲取構造函數類型的實例類型。
源碼實現:
// 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
複製代碼
這個類型能夠用來過濾類型中的 null
及 undefined
類型。
源碼實現:
// 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;
複製代碼
該類型能夠得到函數的參數類型組成的元組類型。
源碼實現:
// 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]
。
該類型的做用是得到類的參數類型組成的元組類型,源碼:
// 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
就是 Person
中 constructor
的參數 firstName
和 lastName
的類型所組成的元組類型 [string, string]
。
下面是一些可能會常常用到,可是 TS 沒有內置的一些類型別名:
有時候咱們想要繼承某個接口,可是又須要在新接口中將某個屬性給 overwrite 掉,這時候經過 Pick
和 Exclude
就能夠組合出來 Omit
,用來忽略對象某些屬性功能:
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
// 使用
type Foo = Omit<{name: string, age: number}, 'name'> // -> { age: number }
複製代碼
將 T 的全部屬性的 readonly
移除:
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
}
複製代碼
內置的 Partial 有個侷限性,就是隻支持處理第一層的屬性,若是是嵌套多層的就沒有效果了,不過能夠以下自定義:
type PowerPartial<T> = {
// 若是是 object,則遞歸類型
[U in keyof T]?: T[U] extends object
? PowerPartial<T[U]>
: T[U]
};
複製代碼
相同的屬性名稱,但使值是一個 Promise
,而不是一個具體的值:
type Deferred<T> = {
[P in keyof T]: Promise<T[P]>;
};
複製代碼
爲 T
的屬性添加代理
type Proxify<T> = {
[P in keyof T]: { get(): T[P]; set(v: T[P]): void }
};
複製代碼
若有疑問,歡迎斧正!