原文連接:github.com/whinc/blog/…html
編寫 TypeScript(後面簡稱TS)應用是一個與類型鬥爭的過程,你須要使用 TS 提供的類型工具經過不一樣的組合來精確描述你的目標。描述越精確,類型約束和提示越準確,潛在錯誤越少。反之,描述越模糊(如any
一把唆),TS 能提供的類型輔助就越少,潛在的錯誤也就越多。如何描寫精確的類型描述須要掌握 TS 的基礎概念,同時掌握常見技巧和類型工具,前者能夠閱讀官網的 TypeScript 手冊 學習,後者能夠經過本文學習部分,後續進階學習就靠多實踐多總結了。git
這節主要介紹一些基礎的類型工具,這是全部高級類型的基石。github
typeof T
- 獲取變量的類型typeof
能夠獲取 JS 變量的類型,它是 JS 變量空間向 TS 類型空間轉換的橋樑,有了它咱們能夠從已有的變量中抽取類型進行進一步處理。typescript
const person = {
name: "jim",
age: 99
}
type Person = typeof person
// type Person = {
// name: string;
// age: number;
// }
複製代碼
keyof T
- 獲取類型的鍵keyof
可獲取目標類型的鍵,返回的是string | number | symbol
的子類型。編程
interface Person {
name: string
age: number
}
type K = keyof Person
// type K = "name" | "age"
複製代碼
進一步閱讀:數組
T[K]
- 類型映射,獲取類型的值配合keyof
一塊兒使用獲取一組 key 對應的 valueapp
interface Person {
name: string
age: number
}
type V = Person[keyof Person]
// type V = "string" | "number"
複製代碼
in T
- 遍歷類型的鍵配合索引訪問類型一塊兒使用,用於表示目標類型的 key函數
interface Person {
name: string
age: number
}
type Partial<T> = { [P in keyof T]?: T[P]}
type PartialPerson = Partial<Person>
// type PartialPerson = {
// name?: string | undefined;
// age?: number | undefined;
// }
複製代碼
T extends U ? X : Y
- 條件類型extends
除了用在繼承類時會使用,還能夠用於判斷一個類型是否比另外一個類型的父類型,並根據判斷結果執行不一樣的類型分支,其使得 TS 類型具有了必定的編程能力。工具
extends
條件判斷規則以下:若是T
能夠賦值給U
返回X
,不然Y
,若是 TS 沒法肯定T
是否能夠賦值給U
,則返回X | Y
。post
type isString<T> = T extends string ? true : false
type T1 = isString<number> // false
type T2 = isString<string> // true
複製代碼
進一步閱讀
infer T
- 類型推斷在extends
條件類型的子句中,可使用infer T
來捕獲指定位置的類型(該類型由 TS 編譯器推斷),在infer
後面的子句中可使用捕獲的類型變量。配合extends
條件類型,截取符合條件的目標的某部分類型。
type ParseInt = (n: string) => number
// 若是是類型 T 是函數,則 R 會捕獲其返回值類型並返回 R,不然返回 any
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
type R = ReturnType<ParseInt> // number
type GetType<T> = T extends (infer E)[] ? E : never
type E = GetType<['a', 100]>
複製代碼
never
never
與類型T
(T
是除unknown
外的其餘任意類型)union 後結果是類型T
,利用never
的這個特色能夠實現類型消除,例如將某個類型先轉換成never
,而後再與其餘類型 union。
type a = string
type b = number
type c = never
type d = a | b |c
// type d = string | number
type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<string | number, string> // number
複製代碼
TS 內置了一些經常使用的類型轉換工具,熟練掌握這些工具類型不只能夠簡化類型定義,並且能夠基於此構建更復雜的類型轉換。
下面是 TS 內置的全部類型工具,我加了下注釋和示例方便理解,你能夠先只看示例,測試下可否自行寫出對應的類型實現(Playground)。
/** * 使 T 的全部屬性變爲爲可選的 * * Partial<{name: string}> // {name?: string | undefined} */
type Partial<T> = {
[P in keyof T]?: T[P];
};
/** * 使類型 T 的全部屬性變爲必需的 * * Required<{name?: string}> // {name: string} */
type Required<T> = {
[P in keyof T]-?: T[P];
};
/** * 使類型 T 的全部屬性變爲只讀的 * * Readonly<{name: string}> // {readonly name: string} */
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
/** * 從類型 T 中挑出全部屬性名出如今類型 K 中的屬性 * * Pick<{name: string, age: number}, 'age'> // {age: number} */
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
/** * 構造一個 key-value 類型,其 key 是類型 K, value 是類型 T * * const map: Record<string, number> = {a: 1, b: 2} */
type Record<K extends keyof any, T> = {
[P in K]: T;
};
/** * 從類型 T 中剔除類型 U * * Exclude<'a' | 'b', 'a'> // 'b' */
type Exclude<T, U> = T extends U ? never : T;
/** * 從類型 T 中挑出類型 U * * Extract<string | number, number> // number */
type Extract<T, U> = T extends U ? T : never;
/** * 從類型 T 中剔除全部屬性名出如今類型 K 中的屬性 * * Omit<{name: string, age: number}, 'age'> // {name: number} */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
/** * 剔除類型 T 中的 null 和 undefined 子類型 * * NonNullable<string | null | undefined> // string */
type NonNullable<T> = T extends null | undefined ? never : T;
/** * 獲取函數的參數元組(注意是元組不是數組) * * Parameters<(name: string, age: number) => void> // [string, number] */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
/** * 獲取構造函數的參數元組 * * class Person { constructor(name: string, age: number) { } } * ConstructorParameters<typeof Person> // [string, number] * * TS 中類有兩個方面:實例面、靜態面 * typeof Person 表示類的靜態面類型 * Person 表示類的靜態面實例,如構造函數、靜態方法 * Person 也表示類實例的類型,如成員變量、成員方法 * new Person 表示類的實例 */
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
/** * 獲取函數的返回類型 * * ReturnType<() => string> // string */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
/** * 獲取構造函數的返回類型,即類的實例的類型 * * class Person { constructor(name: string, age: number) { } } * InstanceType<typeof Person> // Person */
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
複製代碼
除了 TS 內置的類型工具外,還有一些第三方開發的類型工具,提供了更多類型轉換工具,例如 ts-toolbelt —— TS 版"lodash"庫。
下面是從 ts-toolbelt 挑選的部分示例,更多工具類型請查看它的官網。
import type { Object } from 'ts-toolbelt'
// 使對象的部分屬性變爲可選
type T1 = Object.Optional<{ a: string; b: number }, 'a'>
// type T1 = {
// a?: string | undefined;
// b: number;
// }
// 合併兩個對象,前面對象爲 undefined 的屬性被後面對象對應屬性覆蓋
type T2 = Object.MergeUp<{ a: 'a1', b?: 'b1' }, { a: 'a2', b: 'b2' }>
// type T2 = {
// a: "a1";
// b: "b1" | "b2";
// }
複製代碼
掌握基礎概念後,可能依然沒法寫出精確的類型描述,由於這些概念僅僅停留在單個概念的使用,須要進一步實踐練習,纔可能融會貫通。下面蒐集了一些 TS 的類型轉換案例(題目),能夠從中學習一些解題思路和代碼實現。
問題 假定對象的全部值都是數組類型,例如:
const data = {
a: ['x', 'y', 'z'],
b: [1, 2, 3]
} as const
複製代碼
要求獲取上述對象值中的數組元素的類型,例如:
type TElement = "x" | "y" | "z" | 3 | 1 | 2
複製代碼
解題思路 首先拿到對象的值類型,而後經過數組下標獲取數組元素的類型。
參考代碼
type GetValueElementType<T extends { [key: string]: ReadonlyArray<any> }> = T[keyof T][number]
type TElement = GetValueElementType<typeof data>
複製代碼
擴展
若是對象的值不都是數組類型呢?
例以下面這樣
const data = {
a: ['x', 'y', 'z'],
b: [1, 2, 3],
c: 100
} as const
複製代碼
解題思路:首先依然是拿到對象的值類型,而後過濾出數組類型,最後取數組的元素類型
// 實現1:經過 extends 判斷對象的值類型,經過數組下標獲取元素類型
type GetValueElementType<T> = { [K in keyof T]: T[K] extends ReadonlyArray<any> ? T[K][number] : never }[keyof T]
// 實現2:經過 extends 判斷對象的值類型,經過 infer 推斷,獲取數組元素類型
type GetValueElementType<T> = { [K in keyof T]: T[K] extends ReadonlyArray<infer E> ? E : never }[keyof T]
複製代碼
案例部分未完待續。。。