你們用過 Typescript
都清楚,不少時候咱們須要提早聲明一個類型,再將類型賦予變量。前端
例如在業務中,咱們須要渲染一個表格,每每須要定義:git
interface Row { user: string email: string id: number vip: boolean // ... } const tableDatas: Row[] = [] // ...
有時候咱們也須要表格對應的搜索表單,須要其中一兩個搜索項,若是剛接觸 typescript 的同窗可能會馬上這樣寫:github
interface SearchModel { user?: string id?: number } const model: SearchModel = { user: '', id: undefined }
這樣寫會出現一個問題,若是後面id 類型要改爲 string
,咱們須要改 2 處地方,不當心的話可能就會忘了改另一處。因此,有些人會這樣寫:面試
interface SearchModel { user?: Row['user'] id?: Row['id'] }
這當然是一個解決方法,但事實上,咱們前面已經定義了 Row
類型,這實際上是能夠更優雅地複用的:typescript
const model: Partial<Row> = { user: '', id: undefined } // 或者須要明確指定 key 的,能夠 const model2: Partial<Pick<Row, 'user'|'id'>>
這樣一來,不少狀況下,咱們能夠儘可能少地寫重複的類型,複用已有類型,讓代碼更加優雅容易維護。後端
上面使用到的 Partial
和 Pick
都是 typescript 內置的類型別名。下面給你們介紹一下 typescript 經常使用的內置類型,以及自行拓展的類型。數組
將類型 T 的全部屬性標記爲可選屬性微信
type Partial<T> = { [P in keyof T]?: T[P]; };
使用場景:運維
// 帳號屬性 interface AccountInfo { name: string email: string age: number vip: 0|1 // 1 是vip ,0 是非vip } // 當咱們須要渲染一個帳號表格時,咱們須要定義 const accountList: AccountInfo[] = [] // 但當咱們須要查詢過濾帳號信息,須要經過表單, // 但明顯咱們可能並不必定須要用到全部屬性進行搜索,此時能夠定義 const model: Partial<AccountInfo> = { name: '', vip: undefind }
與 Partial 相反,Required 將類型 T 的全部屬性標記爲必選屬性函數
type Required<T> = { [P in keyof T]-?: T[P]; };
將全部屬性標記爲 readonly, 即不能修改
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
從 T 中過濾出屬性 K
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
使用場景:
interface AccountInfo { name: string email: string age: number vip?: 0|1 // 1 是vip ,0 是非vip } type CoreInfo = Pick<AccountInfo, 'name' | 'email'> /* { name: string email: stirng } */
標記對象的 key value類型
type Record<K extends keyof any, T> = { [P in K]: T; };
使用場景:
// 定義 學號(key)-帳號信息(value) 的對象 const accountMap: Record<number, AccountInfo> = { 10001: { name: 'xx', email: 'xxxxx', // ... } } const user: Record<'name'|'email', string> = { name: '', email: '' }
// 複雜點的類型推斷 function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> const names = { foo: "hello", bar: "world", baz: "bye" }; // 此處推斷 K, T 值爲 string , U 爲 number const lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }
移除 T 中的 U 屬性
type Exclude<T, U> = T extends U ? never : T;
使用場景:
// 'a' | 'd' type A = Exclude<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >
乍一看好像這個沒啥卵用,可是,咱們經過一番操做,以後就能夠獲得 Pick
的反操做:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> type NonCoreInfo = Omit<AccountInfo, 'name' | 'email'> /* { age: number vip: 0|1, } */
Exclude
的反操做,取 T,U二者的交集屬性
type Extract<T, U> = T extends U ? T : never;
使用 demo:
// 'b'|'c' type A = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >
這個看起來沒啥用,實際上還真沒啥卵用,應該是我才疏學淺,還沒發掘到其用途。
排除類型 T 的 null
| undefined
屬性
type NonNullable<T> = T extends null | undefined ? never : T;
使用 demo
type A = string | number | undefined type B = NonNullable<A> // string | number function f2<T extends string | undefined>(x: T, y: NonNullable<T>) { let s1: string = x; // Error, x 可能爲 undefined let s2: string = y; // Ok }
獲取一個函數的全部參數類型
// 此處使用 infer P 將參數定爲待推斷類型 // T 符合函數特徵時,返回參數類型,不然返回 never type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
使用demo:
interface IFunc { (person: IPerson, count: number): boolean } type P = Parameters<IFunc> // [IPerson, number] const person01: P[0] = { // ... }
另外一種使用場景是,快速獲取未知函數的參數類型
import {somefun} from 'somelib' // 從其餘庫導入的一個函數,獲取其參數類型 type SomeFuncParams = Parameters<typeof somefun> // 內置函數 // [any, number?, number?] type FillParams = Parameters<typeof Array.prototype.fill>
相似於 Parameters<T>
, ConstructorParameters 獲取一個類的構造函數參數
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
使用 demo:
// string | number | Date type DateConstrParams = ConstructorParameters<typeof Date>
獲取函數類型 T 的返回類型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
使用方式和 Parameters<T>
相似,再也不贅述
獲取一個類的返回類型
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
使用方式和 ConstructorParameters<T>
相似,再也不贅述
使用 typescript
有時候須要重寫一個庫提供的 interface 的某個屬性,可是重寫 interface
有可能會致使衝突:
interface Test { name: string say(word: string): string } interface Test2 extends Test{ name: Test['name'] | number } // error: Type 'string | number' is not assignable to type 'string'.
那麼能夠經過一些 type 來曲線救國實現咱們的需求:
// 原理是,將 類型 T 的全部 K 屬性置爲 any, // 而後自定義 K 屬性的類型, // 因爲任何類型均可以賦予 any,因此不會產生衝突 type Weaken<T, K extends keyof T> = { [P in keyof T]: P extends K ? any : T[P]; }; interface Test2 extends Weaken<Test, 'name'>{ name: Test['name'] | number } // ok
有時候須要
const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const; // TS 3.4 type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs'] type Suit = SuitTuple[number]; // union type : 'hearts' | 'diamonds' | 'spades' | 'clubs'
enum 的 key 值 union
enum Weekday { Mon = 1 Tue = 2 Wed = 3 } type WeekdayName = keyof typeof Weekday // 'Mon' | 'Tue' | 'Wed'
enum 沒法實現value-union , 但能夠 object 的 value 值 union
const lit = <V extends keyof any>(v: V) => v; const Weekday = { MONDAY: lit(1), TUESDAY: lit(2), WEDNESDAY: lit(3) } type Weekday = (typeof Weekday)[keyof typeof Weekday] // 1|2|3
前面咱們講到了 Record 類型,咱們會經常使用到
interface Model { name: string email: string id: number age: number } // 定義表單的校驗規則 const validateRules: Record<keyof Model, Validator> = { name: {required: true, trigger: `blur`}, id: {required: true, trigger: `blur`}, email: {required: true, message: `...`}, // error: Property age is missing in type... }
這裏出現了一個問題,validateRules
的 key 值必須和 Model
所有匹配,缺一不可,但實際上咱們的表單可能只有其中的一兩項,這時候咱們就須要:
type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>> const validateRules: PartialRecord<keyof Model, Validator> = { name: {required: true, trigger: `blur`} }
這個例子組合使用了 typescript
內置的 類型別名 Partial
和 Partial
。
解壓抽離關鍵類型
type Unpacked<T> = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T; type T0 = Unpacked<string>; // string type T1 = Unpacked<string[]>; // string type T2 = Unpacked<() => string>; // string type T3 = Unpacked<Promise<string>>; // string type T4 = Unpacked<Promise<string>[]>; // Promise<string> type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
事實上,基於已有的類型別名,還有新推出的 infer
待推斷類型,能夠探索出各類各樣的複雜組合玩法,這裏再也不多說,你們能夠慢慢探索。
感謝閱讀!
本文首發於 github 博客
如文章對你有幫助,你的 star 是對我最大的支持
插播廣告:
深圳 Shopee 長期內推
崗位:前端,後端(要轉go),產品,UI,測試,安卓,IOS,運維 全都要。
薪酬福利:20K-50K😳,7點下班😏(劃重點),免費水果😍,免費晚餐😊,15天年假👏,14天帶薪病假。 點擊查看詳情 簡歷發郵箱:chenweiyu6909@gmail.com 或者加我微信:cwy13920,實時反饋面試進度哦。