今天的夜點心來談一談怎麼寫 TS 中的條件範型git
在 函數式夜點心:Monad 中咱們提到過 Promise
的 then
方法是一種同時承載了 map
和 chain
功能的方法github
map
功能:當接收的函數的返回值的類型不是 Promise
時,返回包含了該值的 Promise
chain
功能:當接受的函數返回值的類型是 Promise
時,直接返回該值如今假設咱們須要實現一個名爲 promisify
函數,它的功能有些相似 then
方法:接受一個值,若是它已是 Promise
了,就直接返回;若是不是,就把它包在一個 Promise
中返回。這個需求實現起來不難:typescript
function promisify<T> (input: T) {
if (input instanceOf Promise) {
return input;
}
return Promise.resolve(input);
}
複製代碼
上面的實現經過範型 T
,聲明瞭入參 input
的類型。而Typescript 經過本身的類型推導,只能得出 promisify
的返回類型是 (T & Promise<any>) | Promise<T>
,並不能像咱們的函數邏輯那樣,知道應該在 T 是 Promise 的時候返回 T 類型,不然返回 Promise<T>
類型。這樣類型的定義與函數的邏輯沒有匹配上,會使得 promisify
函數變得很難用:咱們不得不每次都手動斷言它的返回類型,繁瑣且易錯。數組
爲了讓 Typescript 可以根據入參的類型自動判斷出出參的類型,咱們須要用到條件範型:promise
條件範型經過相似三元表達式的 extends ? :
的結構和 infer
關鍵字,幫助咱們寫出可以根據條件成立與否進行類型轉換的工具範型。函數
T extends U ? A : B
的結構判斷一個類型 T
是不是類型 U
的子類型,是則返回 A
,不是返回 B
,例如:工具
type Condition<T> = T extends { name: string } ? string : number;
type Test1 = Condition<{ name: string; value: number }>; // string
type Test2 = Condition<{ value: number }>; // number;
複製代碼
經過條件範型的三元表達式結構,咱們很快就能解決上面提到的問題:post
function promisify<T> (input: T): T extends Promise ? T : Promise<T> {
// 函數的具體實現
}
複製代碼
如今假設咱們的需求變得更復雜了一點,須要把 Promise
中的值包在一個 { value: T } 的結構中返回,像下面實現的這樣,那麼它的返回類型又應該怎麼聲明呢?優化
function promisify2<T> (input: T) {
if (input instanceof Promise) {
return input.then(value => ({ value }));
}
return Promise.resolve({ value: input });
}
複製代碼
這時僅僅使用 extends ? :
的結構就不夠了。由於當 T
是 Promise
的子類型時,咱們須要從 T
中「抽取」出它的範型 U
並把它從新包裝起來返回出來。即假設 T = Promise<U>
,咱們須要得到 U
。這時就須要用到 infer
關鍵字,infer 意爲推斷,能夠用來解構提取咱們須要的類型,用法很簡單:ui
extends
和 ?
之間使用 infer
infer {類型變量名}
放置在須要提取的類型的位置?
和 :
之間使用推斷獲得的類型變量來構造須要返回的類型以下的 Unpromise
條件範型就在 T 是 Promise 時經過 infer
關鍵字提取了 Promise 的範型
type Unpromise<T> = T extends Promise<infer U> ? U : T;
type Test1 = Unpromise<number>; // number
type Test2 = Unpromise<Promise<string>>; // string
複製代碼
經過 infer
咱們就可以完成上面 promisify2
函數的返回類型的聲明:
function promisify2<T>( input: T ): T extends Promise<infer U> ? Promise<{ value: U }> : Promise<{ value: T }>
{
// 具體的實現
};
複製代碼
上述的類型聲明由於邏輯的複雜化變得冗長而難讀,咱們能夠經過適當的類型拆解來優化它的可讀性,同時也能提升類型的複用性:
type Container<T> = Promise<{ value: T }>;
type Unpromise<T> = T extends Promise<U> ? U : T;
function promisify2<T>(input: T): Container<Unpromise<T>> {
// 具體的實現
}
複製代碼
// 提取數組項的類型
type Unarray<T> = T extends (infer U)[] ? U : never;
// 提取函數的返回值類型(TS 已內置)
type ReturnType<T> = T extends ((...params: any[]) => infer U) ? U : never;
// 提取函數的入參類型(TS 已內置)
type Parameters<T> = T extends ((...params: P) => infer P) ? P : never;
// 元組第一項的類型,可用在 Hooks 風格的 React 組件中
type Head<T> = T extends [infer H, ...any[]] ? H : never;
複製代碼
上面這些例子中的 never
至關於不符合條件則不返回任何類型。
以上就是「條件範型」的相關內容。條件範型使得範型具備函數通常的靈活性,方便咱們定義出與實現邏輯更爲匹配的類型,寫出更優雅強大的 TS 代碼。