TS 有個非常好用的功能就是型別別名。
型別別名會給一個型別起個新名字。型別別名有時和介面很像,但是可以作用於原始值,聯合型別,元組以及其它任何你需要手寫的型別。
一些關鍵字
使用型別別名可以實現很多複雜的型別,很多複雜的型別別名都需要藉助關鍵字,我們先來了解一下幾個常用的關鍵字:
extends
extends
可以用來繼承一個類,也可以用來繼承一個 interface
,但還可以用來判斷有條件型別:
T extends U ? X : Y;
複製程式碼
上面的型別意思是,若 T
能夠賦值給 U
,那麼型別是 X
,否則為 Y
。
原理是令 T'
和 U'
分別為 T
和 U
的例項,並將所有型別引數替換為 any
,如果 T'
能賦值給 U'
,則將有條件的型別解析成 X
,否則為Y
。
上面的官方解釋有點繞,下面舉個例子:
type Words = 'a'|'b'|"c";
type W<
T>
= T extends Words ? true : false;
type WA = W<
'a'>
;
// ->
truetype WD = W<
'd'>
;
// ->
false複製程式碼
a
可以賦值給 Words
型別,所以 WA
為 true
,而 d
不能賦值給 Words
型別,所以 WD
為 false
。
typeof
在 JS 中 typeof
可以判斷一個變數的基礎資料型別,在 TS 中,它還有一個作用,就是獲取一個變數的宣告型別,如果不存在,則獲取該型別的推論型別。
舉兩個栗子:
interface Person {
name: string;
age: number;
location?: string;
}const jack: Person = {
name: 'jack', age: 100
};
type Jack = typeof jack;
// ->
Personfunction 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複製程式碼
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.tstype 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.tstype Required<
T>
= {
[P in keyof T]-?: T[P];
};
複製程式碼
其中 -?
是代表移除 ?
這個 modifier 的標識。
與之對應的還有個 +?
, 這個含義自然與 -?
之前相反, 它是用來把屬性變成可選項的,+
可省略,見 Partial
。
再擴充一下,除了可以應用於 ?
這個 modifiers ,還有應用在 readonly
,比如 Readonly
.
Readonly
這個型別的作用是將傳入的屬性變為只讀選項。
// node_modules/typescript/lib/lib.es5.d.tstype Readonly<
T>
= {
readonly [P in keyof T]: T[P];
};
複製程式碼
給子屬性新增 readonly
的標識,如果將上面的 readonly
改成 -readonly
, 就是移除子屬性的 readonly
標識。
Pick
這個型別則可以將某個型別中的子屬性挑出來,變成包含這個型別部分屬性的子型別。
原始碼實現如下:
// node_modules/typescript/lib/lib.es5.d.tstype 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.tstype 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.tstype 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.tstype 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'
了。
ReturnType
該型別的作用是獲取函式的返回型別。
原始碼的實現
// node_modules/typescript/lib/lib.es5.d.tstype 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.tsinterface 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.tstype 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>
;
// Ctype T21 = InstanceType<
any>
;
// anytype T22 = InstanceType<
never>
;
// anytype T23 = InstanceType<
string>
;
// Errortype T24 = InstanceType<
Function>
;
// Error複製程式碼
NonNullable
這個型別可以用來過濾型別中的 null
及 undefined
型別。
原始碼實現:
// node_modules/typescript/lib/lib.es5.d.tstype 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.tstype 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.tstype 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 沒有內建的一些型別別名:
Omit
有時候我們想要繼承某個介面,但是又需要在新介面中將某個屬性給 overwrite 掉,這時候通過 Pick
和 Exclude
就可以組合出來 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
}
};
複製程式碼
如有疑問,歡迎斧正!