前言:在編寫 typescript 應用的時候,有時候咱們會但願複用或者構造一些特定結構的類型,這些類型只從 typescript 靠內建類型和 interface、class 比較難以表達,這時候咱們就須要用到類型推導, 而討論類型推導, 則離不開泛型和推斷(#infer), 本文咱們只討論泛型html
上一篇typescript
從形式上看, typescript 中的泛型如同大多數語言(不包括還沒有實現的 Go :P)裏的泛型:數組
// one constrain for array-like object
interface ArrayLike<T> {
readonly length: number;
readonly [n: number]: T;
}複製代碼
上面的 ArrayLike
表達了類數組結構, 它表明的對象的特徵是:bash
length
字段;array-like 對象是 Javascript 中很古老的對象, 如聲明爲 function (...) {...}
格式的函數, 其內部的 arguments 變量就是典型的 array-like 對象.app
泛型每每能夠用在這些場景:函數
接下來咱們依次說明, 在這些場景中, 類型推導如何發揮它的威力.學習
在利用泛型作類型推導時, 切記:ui
第 2 點可能有點難以理解, 咱們先略過, 在下一篇, 咱們會明白這句話的含義.es5
在上一篇層提到, 咱們能夠經過 keyof
提取一個 interface 的全部鍵名, 當引入泛型後, keyof 還能夠作更有趣的事情:spa
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};複製代碼
Partial
的做用是: 對 T(T 須要是可被當作 interface 的類型), 求其全部的鍵名, 並依賴鍵名, 獲得一個結構徹底相同, 但其全部鍵均可選 的新 interface.
好比, 對與 Form, UninitForm 能夠是它的全鍵可選項版本:
interface Form {
name: string
age: number
sex: 'male' | 'female' | 'other'
}
type UninitForm = Partial<Form>複製代碼
則 UninitForm 等價於:
{
name?: string;
age?: number;
sex?: "male" | "female" | "other";
}複製代碼
相反, 若是你已經有了 UninitForm, 則你能夠得靠 Required
到它的全鍵必需化版本:
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};複製代碼
interface UninitForm {
name?: string;
age?: number;
sex?: "male" | "female" | "other";
}
type AllKeyRequriedForm = Required<UninitForm>複製代碼
結合 readonly, 咱們能夠把一個 interface 裏全部的鍵轉化爲只讀
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};複製代碼
對於 js 而言, extends
是擴展類的保留字; 而在 typescript 中, 當 extends 出現的以下的場景時, 它意味着類型推導:
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;複製代碼
形如 T extends [DEP] ? [RESULT1] : [RESULT2]
的表達式, 是 typescript 中的一種類型推導式, 它的規則是:
若泛型 T 必須知足 [DEP] 的約束(即 T extends [DEP]
爲 true
), 則表達式結果爲 [RESULT1]; 反之表達式結果爲 [RESULT2]:
T extends [DEP]
爲 true
, 反之爲 false
T extends [DEP]
爲 true
, 反之爲 false
T extends [DEP]
恆爲 true
按照這些規則, 咱們來分析一下這個 Exclude
.
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;複製代碼
分析可知:
Exclude
中有兩個必要的泛型標記 T
和 U
(由於它們都未提供默認泛型)咱們來應用一下 Exclude
type NoOne = Exclude<1 | 2 | 3, 1> // NoOne = 2 | 3複製代碼
另外, 爲了說明上面的第 4 點, 咱們能夠寫一個毫無用處的 NotRealExclude
;
type NotRealExclude<T, U> = T extends U ? U : T;
type Orig = NotRealExclude<1 | 2 | 3, 1> // Orig = 1 | 2 | 3複製代碼
因爲 NotRealExclude
在 T 不符合 U 的時候返回 U, 而在 T 符合 U 的時候又返回 T, 最終的結果是: 組成 T 的全部類型又被從新組裝了起來.
在瞭解了 extends
的基本用法後, 咱們來看更多的例子:
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};複製代碼
正如咱們經常使用的 pick 函數 能夠提取對象中特定的鍵值對, Pick
也能夠提取 T 中特定的鍵值定義.
interface Person {
name: string
age: number
sex?: "male" | "female" | "other";
}
/**
* equivalent to { name: string }
*/
type SimplePersonInfo = Pick<Person, 'name'>複製代碼
既然能夠 Pick
, 那也能夠 Omit
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;複製代碼
/**
* equivalent to { name: string, age: number }
*/
type SimplePersonInfo = Omit<Person, 'sex'>複製代碼
咱們知道, 在對象的索引中, in
關鍵字能夠用於從聯合類型中提取類型, 做爲 interface 的鍵名, 好比:
type Ks = 'a' | 'b' | 'c'
type KObject = {
[P in Ks]: any
}複製代碼
則 KObject 能夠包含 'a'
, 'b'
, 'c'
三種類型的鍵名.
結合 extends, 咱們能夠輕鬆地從一個 interface 構建具備一樣類型的值的字典, 這就是 Record
:
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};複製代碼
// construct one family with 3 keys: parent, mom, child
type Family = Record<'parent' | 'mom' | 'child', Person>複製代碼
這等價於
interface Family {
parent: Person,
mom: Person,
child: Person
}複製代碼
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;複製代碼
至此,
但到目前爲止, 咱們處理的場景都限與非函數的 interface(class)/type, 對於函數的 interface, 咱們可否進行一些特殊處理, 好比, 對一個已有的函數定義, 提取其第 2 個參數的參數類型? 對於下面這個 func, 咱們可否提取出 arg2 的類型?
interface func () {
(arg1: string, arg2: {
bar: string
}): void
}複製代碼
下一篇咱們會討論, 如何使用 infer 關鍵字達成這一目標.
基於泛型的類型推導, 理論上從 typescript 2.8 開始(實際上更早, 但 typescript 2.8/3.5 是具備里程碑意義的版本, 故以此劃分)就能夠實現了, 從 typescript 3.5, 官方內置了一些用於推導的的類型(#type)和接口(#interface), 這些是咱們用於學習類型推導的良好案例. 本文用到的全部例子, 都來自於 typescript 內置的 lib.es5.d.ts
.
注意 對於泛型, T
每每是用做泛型標記的第一個選擇, T
之於泛型, 比如 foo
之於樣例代碼