TypeScript 技巧集錦

原文連接:github.com/whinc/blog/…html

image

編寫 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 | Ypost

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與類型TT是除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 的類型轉換案例(題目),能夠從中學習一些解題思路和代碼實現。

No.1

問題 假定對象的全部值都是數組類型,例如:

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]
複製代碼

案例部分未完待續。。。

參考

相關文章
相關標籤/搜索