泛型编程 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,主要作用是对不特定数据类型的支持,以实现函数、接口(Interface)、类的复用。
泛型类似于函数,函数的本质是推后执行(调用),部分待定(函数参数)的代码,泛型的本质是推后执行(调用),部分待定(泛型参数)的类型。
1 2 type F<A, B> = A | Btype Result = F<string , number >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface List <A> { [index : number ]: A } type X = List <string >type X = { [index : number ]: string } interface Hash <V = string > { [key : string ]: V }
泛型中使用 extends
,判断是否为子类型。
1 2 3 4 5 6 7 8 9 10 11 type Person = { id : number ; name : string ; age?: number ; } type LikeString <T> = T extends string ? true : false type LikeNumber <T> = T extends number ? 1 : 2 type LikePerson <T> = T extends Person ? 'yes' : 'no' type Result = LikeString <'a' >
若 T
为联合类型,则会使用泛型分配律,分开计算。
1 2 3 4 5 type ToArray <T> = T extends unknown ? T[] : never type Result = ToArray <string | number > type Result = (string extends unknown ? string [] : never ) | (number extends unknown ? number [] : never )
若 T
为 never
,则直接返回 never
,不会进入条件运算。
1 2 type Result = LikeString <never > type Result = ToArray <never >
上述规则只对泛型有效,不在泛型中的联合类型和 never
,按正常规则计算。
1 type Result = never extends unknown ? 1 : 2
1 2 type GetKeys <T> = keyof T type Result = GetKeys <Person >
在泛型中使用泛型约束 extends keyof
。
1 2 3 4 type GetKeyType <T, K extends keyof T> = T[K] type Result = GetKeyType <Person , 'age' >
映射类型(Mapped Types)将现有类型根据某种映射规则转换为一种新的类型。映射类型常通过 keyof
、in
、修饰符(?
、readonly
、-readonly
、-?
)和条件类型(Conditional Types)实现。以几种内置的工具类型 Partial
、Required
、Record
、Exclude
、Extract
、Pick
、Omit
为例,看其如何使用映射类型的。
1 2 3 4 5 6 7 8 9 10 11 12 13 type X1 = Readonly <Person >type Readonly <T> = { readonly [K in keyof T]: T[K] } type Mutable <T> = { -readonly [K in keyof T]: T[K] }
1 2 3 4 5 6 type Result = Partial <Person >type Partial <T> = { [K in keyof T]?: T[K] }
1 2 3 4 5 6 type Result = Required <Person >type Required <T> = { [K in keyof T]-?: T[K] }
1 2 3 4 5 6 7 type Result = Record <string , string >type Record <Key extends string | number | symbol , Val > = { [K in Key ]: Val }
1 2 3 4 5 6 type Result = Exclude <1 | 2 | 3 , 1 | 2 > type Exclude <A, B> = A extends B ? never : A
1 2 3 4 type Result = Extract <1 | 2 | 3 , 2 | 4 > type Extract <A, B> = A extends B ? A : never
1 2 3 4 5 6 type Result = Pick <Person , 'name' | 'age' >type Pick <T, Key extends keyof T> = { [K in Key ]: T[K] }
1 2 3 4 5 6 7 8 9 10 11 12 13 type Result = Omit <Person , 'name' | 'age' >type Omit <T, Key > = { [K in keyof T as (K extends Key ? never : K)]: T } type Omit <T, Key extends keyof T> = Pick <T, Exclude <keyof T, Key >>
类型体操 JavaScript 中可以对值 进行各种运算(算术运算、逻辑运算、比较运算…)以及循环、判断流的程控制和函数、面向对象等高级特性,如果把 TypeScript 的类型系统当作一门语言,TypeScript 可以对类型 进行各种运算以及循环、判断流程控制和泛型等高级特性。
以计算斐波那契数列第 n 项值为例,使用 TypeScript 类型实现和 JavaScript 解释为以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type Fibonacci < T extends number , TArray extends ReadonlyArray <unknown > = [unknown , unknown , unknown ], PrePre extends ReadonlyArray <unknown > = [unknown ], Pre extends ReadonlyArray <unknown > = [unknown ], > = T extends 1 ? 1 : T extends 2 ? 1 : TArray ['length' ] extends T ? [...Pre , ...PrePre ]['length' ] : Fibonacci <T, [...TArray , unknown ], Pre , [...Pre , ...PrePre ]> type Result = Fibonacci <6 >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function fibonacci (n ) { if (n === 1 || n === 2 ) { return 1 } else { let prePre = 1 let pre = 1 let result = 0 for (let i = 3 ; i <= n; i++) { result = pre + prePre prePre = pre pre = result } return result } } fibonacci (6 )
注:TypeScript 类型体操是纯粹的函数式编程,函数式编程最基本原则是数据不可变,TypeScript 中类型一但声明就不能改变。
体操的基本原理 TypeScript 类型体操的本质是可以对类型进行循环、判断、交叉、联合、泛型等运算和操作。
TypeScript 中除了可以通过递归实现循环外,还可以通过分布式条件类型或映射类型实现,但它们都不能传递类型。
1 2 3 4 type Example <T> = T extends number ? T : never type Result = Example <'1' | '2' | 3 | 4 >
1 2 3 4 5 type Example <T extends string | number > = { [Key in T]: Key } type Result = Example <'1' | '2' | 3 | 4 >
使用三名运算实现条件判断。
1 2 3 4 type A = 1 type B = 1 | 2 type Result = A extends B ? true : false
1 2 3 4 5 6 7 8 9 10 11 12 type A = 1 type B = 1 | 2 type C = 3 type D = 3 | 4 type Result = A extends B ? C extends D ? 'true, true' : 'true, false' : C extends D ? 'false, true' : 'false, false'
1 2 3 4 5 type A = []type IsEmptyArray <Arr extends unknown []> = Arr ['length' ] extends 0 ? true : false type Result = IsEmptyArray <A>
1 2 3 4 5 6 7 8 9 type A = []type IsNotEmptyArray <Arr extends unknown []> = Arr ['length' ] extends 0 ? false : true type Result = IsNotEmptyArray <A>type IsNotEmptyArray <Arr extends unknown []> = Arr extends [...infer X, infer Y] ? true : false
1 2 3 4 5 6 type A = ['ji' , 'ni' , 'tai' , 'mei' ]type Reverse <Arr extends unknown []> = Arr extends [...infer Rest , infer Last ] ? [Last , ...Reverse <Rest >] : Arr type Result = Reverse <A>
注:经测试,元组递归的层数最多 48
层,普通对象的层数限制未测出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type DeepReadonly <T> = { readonly [P in keyof T]: DeepReadonly <T[P]> } interface SomeObject { a : { b : { c : number } } } const obj : DeepReadonly <SomeObject > = {a : {b : {c : 2 }}}obj.a .b .c = 23
TypeScript 中没有 ==
,但是通过 extends
可以实现类似的效果。
1 2 3 4 5 6 7 8 type EqEq <T1 , T2 > = [T1 ] extends [T2 ] ? ([T2 ] extends [T1 ] ? true : false ) : false ; type Result1 = EqEq <5 , 10 >; type Result2 = EqEq <"abc" , "abc" >; type Result3 = EqEq <true , false >; type Result4 = EqEq <never , never >;
1 2 3 type Tuple = ['ji' , 'ni' , 'tai' , 'mei' ]type Result1 = Tuple extends [infer First , ...infer Rest ] ? First : never type Result2 = Tuple extends [infer First , ...infer Rest ] ? Rest : never
元组的基本体操 1 2 3 type A = [1 ]type B = [...A, 2 ]
1 2 3 4 5 6 7 type A = [1 , 2 , 3 , 4 ]type Last <T extends unknown []> = T extends [...unknown [], infer Last ] ? Last : never type Result = Last <A>type Last <T extends unknown []> = T[T['length' ] - 1 ]
1 2 3 4 5 6 7 type A = [1 , 2 , 3 , 4 ]type NotLast <T extends unknown []> = T extends [...infer X, unknown ] ? X : never type Result = NotLast <A>type Last <T extends unknown []> = T[T['length' ] - 1 ]
字符串的基本体操 1 2 3 4 5 6 type A = 'tracy' type B = Capitalize <A> type C = 'ji' | 'ni' | 'tai' | 'mei' type D = Capitalize <C>
除了 Capitalize
首字母大写外,TypeScript 还内置了 Uppercase
全变成大写,Uncapitalize
首字母小写,Lowercase
全变成小写。
1 2 3 4 5 6 type A = 'ji' type B = 'ni' type C = 'tai' type D = 'mei' type X = `${A} ${B} ${C} ${D} `
1 2 3 4 5 6 7 8 9 10 type CanStringified = string | number | bigint | boolean | null | undefined type Stringify <T extends CanStringified > = `${T} ` type Result1 = Stringify <0 > type Result2 = Stringify <-1 > type Result3 = Stringify <0.1 > type Result4 = Stringify <'0.2' >
1 2 3 4 type A = 'ji ni tai mei' type First <T extends string > = T extends `${infer F} ${string } ` ? F : never type Result = First <A>
注:使用模式匹配只能获取第一个字符和其它剩下字符,不能获取最后一个字符,如果想要获取最后一个字符,可转为元组操作。
1 2 3 4 5 6 7 8 type A = 'ji ni tai mei' type LastOfTuple <T extends unknown []> = T extends [...infer _, infer L] ? L : never type StringToTuple <S extends string > = S extends `${infer F} ${infer R} ` ? [F, ...StringToTuple <R>] : [] type LastOfString <S extends string > = LastOfTuple <StringToTuple <S>>type Result = LastOfString <A>
1 2 3 4 5 type StringToUnion <S extends string > = S extends `${infer First} ${infer Rest} ` ? First | StringToUnion <Rest > : never type Result = StringToUnion <'jinitaimei' >
1 2 3 4 5 type StringToTuple <S extends string > = S extends `${infer First} ${infer Rest} ` ? [First , ...StringToTuple <Rest >] : [] type Result = StringToTuple <'jinitaimei' >
Type-Challenges Type-Challenges 中常见问题解法。
1 2 3 4 5 6 7 8 9 10 11 type Pick <T, Key extends keyof T> = { [K in Key ]: T[K] } type Result = Pick <{a : 1 ; b : 2 ; c : 3 }, 'a' >{ [K in 'a' ]: {a : 1 ; b : 2 ; c : 3 }['a' ] }
1 2 3 4 5 6 type Parameters <F extends (...args : any []) => any > = F extends (...args : infer X) => any ? X : never const foo = (arg1 : string , arg2 : number ): void => {}type Result = Parameters <typeof foo>
1 2 3 4 5 6 7 8 type Awaited <P extends PromiseLike <any >> = P extends PromiseLike <infer X> ? X extends PromiseLike <any > ? Awaited <X> : X : never
1 2 3 4 5 6 7 type Zip <A extends any [], B extends any []> = A extends [infer AFirst , ...infer ARest ] ? B extends [infer BFirst , ...infer BRest ] ? [[AFirst , BFirst ], ...Zip <ARest , BRest >] : [] : []
1 2 3 4 5 6 7 8 9 10 11 type IsTuple <T> = [T] extends [never ] ? false : T extends readonly any [] ? number extends T['length' ] ? false : true :false
1 2 3 4 5 6 7 type Join <T extends string [], U extends string | number > = T extends [infer First extends string , ...infer Rest extends string []] ? Rest ['length' ] extends 0 ? First : `${First} ${U} ${Join<Rest, U>} ` : ''