TypeScript 強大的型別別名

MervynZ發表於2019-01-07

TS 有個非常好用的功能就是型別別名。

型別別名會給一個型別起個新名字。型別別名有時和介面很像,但是可以作用於原始值,聯合型別,元組以及其它任何你需要手寫的型別。

一些關鍵字

使用型別別名可以實現很多複雜的型別,很多複雜的型別別名都需要藉助關鍵字,我們先來了解一下幾個常用的關鍵字:

extends

extends 可以用來繼承一個類,也可以用來繼承一個 interface,但還可以用來判斷有條件型別:

T extends U ? X : Y;
複製程式碼

上面的型別意思是,若 T 能夠賦值給 U,那麼型別是 X,否則為 Y

原理是令 T'U' 分別為 TU 的例項,並將所有型別引數替換為 any,如果 T' 能賦值給 U',則將有條件的型別解析成 X,否則為Y

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

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複製程式碼

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,最終結果是將 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.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

這個型別可以用來過濾型別中的 nullundefined 型別。

原始碼實現:

// 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 就是 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 一些你可能不知道的工具泛型的使用及其實現

來源:https://juejin.im/post/5c2f87ce5188252593122c98

相關文章