高级类型 ReturnType 获取函数返回类型 不使用 ReturnType 实现 TypeScript 的 ReturnType 范型。 infer 推断返回类型
1 2 3 4 5 6 7 8 9 10 11 type MyReturnType<T extends (...args: any )=>any > = T extends (...args: any)=> infer R ? R : never const fn = (v: boolean) => { if (v) return 1 else return 2 } type a = MyReturnType <typeof fn>
ReadyOnly2 实现一个通用 MyReadonly2<T, K>,它带有两种类型的参数 T 和 K。
K 指定应设置为 Readonly 的 T 的属性集。如果未提供 K,则应使所有属性都变为只读,就像普通的 Readonly一样。
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 interface Todo { title: string description: string completed: boolean }type MyReadonly2<T ,K extends keyof T = keyof T > = { readonly [P in K ]: T [P ] } & T const todo: MyReadonly2 <Todo , 'titl e' | 'descriptio n'> = { title: "Hey" , description: "foobar" , completed: false , } todo.title = "Hello" todo.description = "barFoo" todo.completed = true
使用映射类型与交叉类型, 先取指定元素加上readonly 然后给非指定类型去除readonly 两者取交集 注意默认值:keyof T
1 2 3 4 5 type MyReadonly2 <T , K extends keyof T = keyof T > = { +readonly [P in keyof T as P extends K ? P : never]: T [P ] } & { -readonly [P in keyof T as P extends K ? never : P ]: T [P ] }
深度 Readonly 实现一个通用的 DeepReadonly,它将对象的每个参数及其子对象递归地设为只读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type X = { x : { a : 1 b : 'hi' } y: 'hey' }type Expected = { readonly x : { readonly a : 1 readonly b : 'hi' } readonly y: 'hey' }type DeepReadonly <T > = { readonly [K in keyof T ]: T [K ] extends object ? DeepReadonly <T [K ]> : T [K ] } // 对象才能继续深度遍历 type todo = DeepReadonly <X > // should be same as `Expected `
元组转集合 实现泛型 TupleToUnion,它覆盖元组的值与其值联合
1 2 3 4 5 6 7 8 9 10 11 12 const a = ['1 ', '2 ', '3 '] as const type Arr = typeof atype TupleToUnion<T extends unknown []> = T [number]type a = TupleToUnion <Arr >
可串联构造器 在 JavaScript 中我们很常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给他附上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 interface Chainable <Tdict ={}> { option: <Tkey extends string , Tvalue>(key: Tkey, value : Tvalue) => Chainable< Tdict & Record<Tkey, Tvalue>>, get : ()=> Tdict } declare const config: Chainableconst result = config .option('foo' , 123 ) .option('name' , 'type-challenges' ) .option('bar' , { value : 'Hello World' }) .get ()interface Result { foo: number name: string bar: { value : string } }
最后一个元素 实现一个通用 Last,它接受一个数组 T 并返回其最后一个元素的类型
1 2 3 4 5 6 7 8 9 type arr1 = ['a', 'b', 'c']type arr2 = [3 , 2 , 1 ]type Last<T extends unknown []> = T extends [infer first, ...(infer Reset )] ? Reset ['lengt h'] extends 0 ? first : Last <Reset > : nevertype tail1 = Last <arr1> type tail2 = Last <arr2>
堆栈操作 实现一个通用 Pop,它接受一个数组 T 并返回一个没有最后一个元素的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type arr1 = ['a', 'b', 'c', 'd']type arr2 = [3 , 2 , 1 ]type Pop<T extends unknown []> = T extends [...(infer Reset ), infer Last ] ? Reset :nevertype Shift<T extends unknown []> = T extends [infer First , ...(infer Reset )] ? Reset :nevertype Push<T extends unknown [], K> = T extends unknown[] ? [...T , K ] :nevertype Unshift<T extends unknown [], K> = T extends unknown[] ? [K , ...T ] :nevertype re1 = Pop <arr1> type re2 = Pop <arr2> type re3 = Shift <arr1> type re4 = Shift <arr2> type re5 = Push <arr1, ['d','123 1']> type re6 = Push <arr2, 4 > type re7 = Unshift <arr1, ['d','123 1']> type re8 = Unshift <arr2, 4 >
PromiseAll 键入函数 PromiseAll,它接受 PromiseLike 对象数组,返回值应为 Promise,其中 T 是解析的结果数组
1 2 3 4 5 6 7 8 9 10 11 12 const promise1 = Promise .resolve(3 );const promise2 = 42 ;const promise3 = new Promise <string >((resolve, reject ) => { setTimeout (resolve, 100 , 'foo' ); });declare function PromiseAll <T extends any []>(values: readonly [...T] ): Promise < { [K in keyof T]: T[K] extends Promise <infer R> ? R : T[K] }>const p = PromiseAll([promise1, promise2, promise3] as const )
Type Lookup 有时,您可能希望根据其属性在并集中查找类型。
在此挑战中,我们想通过在联合 Cat | Dog 中搜索公共 type 字段来获取相应的类型。换句话说,在以下示例中,我们期望 LookUp<Dog | Cat, ‘dog’>获得 Dog,LookUp<Dog | Cat, ‘cat’>获得 Cat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Cat { type : 'ca t' breeds: 'Abyssinia n' | 'Shorthai r' | 'Cur l' | 'Benga l' } interface Dog { type : 'do g' breeds: 'Houn d' | 'Brittan y' | 'Bulldo g' | 'Boxe r' color: 'brow n' | 'whit e' | 'blac k' } type LookUp<T extends { type : any}, K extends T ['typ e']> = T extends {type : K } ? T : never type MyDog = LookUp <Cat | Dog , 'do g'>
Trim Implement TrimLeft which takes an exact string type and returns a new string with the whitespace beginning removed.
1 2 3 4 5 type TrimLeft<T extends string> = T extends `${' ' | '\n' | '\t'}${infer SS }` ? TrimLeft <SS > : T type trimed = TrimLeft <' Hello World '>
trim
1 2 3 4 5 6 7 8 // type TrimLeft<S extends string > = S extends `${' ' | '\n' | '\t' }${infer SS}`?TrimLeft<SS>:S // type TrimRight<S extends string > = S extends `${infer SS}${' ' | '\n' | '\t' }`?TrimRight<SS>:S // type Trim<S extends string > = TrimLeft<TrimRight<S>>type Trim<T extends string > = T extends `${infer SS}${' ' | '\n' | '\t' }` | `${' ' | '\n' | '\t' }${infer SS}` ? Trim<SS> : Ttype trimed = Trim<' Hello World ' > // expected to be 'Hello World'
Capitalize Implement Capitalize which converts the first letter of a string to uppercase and leave the rest as-is.
1 2 3 4 type MyCapitalize<T extends string> = T extends `${infer R }${infer F }` ? `${Uppercase <R >}${F }` :T type capitalized = MyCapitalize <'hello world'>
Replace Implement Replace<S, From, To> which replace the string From with To once in the given string S
1 2 3 4 5 type Replace<T extends string , K extends string , P extends string> = K extends ''? T : T extends `${infer F }${K }${infer L }` ? `${F }${P }${L }` : K type replaced = Replace <'types are fun!', 'fu n', 'awesom e'>
ReplaceAll Implement ReplaceAll<S, From, To> which replace the all the substring From with To in the given string S
1 2 3 4 5 6 type ReplaceAll<T extends string , K extends string , U extends string> = K extends '' ? T : T extends `${infer F }${infer K }${infer L }` ? `${F }${U }${ReplaceAll <L , K , U >}` : T type replaced = ReplaceAll <'t y p e s', ' ', ''>
追加参数
实现一个范型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数
1 2 3 4 5 6 7 8 type Fn = (a: number , b: string ) => number type AppendArgument<T extends (...args: any ) => any , K> = T extends (...args: infer P) => infer R ? (x:K, ...args: P ) => R : never type Result = AppendArgument<Fn, boolean >
Length of String Compute the length of a string literal, which behaves like String#length.
1 2 3 4 5 6 7 8 9 type StringToArray<T extends string> = T extends `${infer F }${infer R }` ? [F , ...StringToArray <R >] : []type StringLength<S extends string> = StringToArray <S >['lengt h']type a = StringLength <'12313 qawqe'>
Flatten In this challenge, you would need to write a type that takes an array and emitted the flatten array type.
1 2 3 4 5 type Flatten<T> = T extends ? : T extends ? : type flatten = Flatten<> //
NativeFlat
使用映射类型的索引获取值 定义一个 NativeFlat 工具类型,支持把数组类型拍平(扁平化)
1 2 3 4 5 6 7 8 9 10 11 12 13 type NativeFlat<T extends any []> = { [K in keyof T]: T[K] extends any [] ? T[K][number ] : T[K] }[number ]type NaiveResult = NaiveFlat<[['a' ], ['b' , 'c' ], ['d' ]]>type DeepNativeFlat<T extends any []> = { [K in keyof T]: T[K] extends any [] ? DeepNativeFlat<T[K]> : T[K] }[number ]type DeepTestResult = DeepFlat<Deep>
AppendObject 1 2 3 4 5 6 7 8 9 10 11 type Test = { id: '1 ' }type CombineObject<T extends Object> = { [K in keyof T ]: T [K ] }type AppendToObject<T extends object , K extends string , P> = CombineObject <T & { [U in K ]: P }>type Result = AppendToObject <Test , 'valu e', 4 >
Absolute 1 2 3 4 5 6 7 8 type Test = -100 ;type Absolute<T extends string | number | bigint> = `${T }` extends `${'-'}${infer R }` ? R : `${T }`type Result = Absolute <Test >;
String to union 1 2 3 4 5 6 type Test = '12 3';type StringToUnion<T extends string> = T extends "" ? never : T extends `${infer F }${infer L }` ? (F | StringToUnion <L >) : T type Result = StringToUnion <Test >;
Merge 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type Foo = { a: number; b: string; };type Bar = { b: number; c: boolean; };type Merge<T extends object , K extends object> = { [P in keyof (T & K )]: P extends keyof K ? K [P ] : P extends keyof T ? T [P ] : never }type a = Merge <Foo , Bar >
CamelCase 1 2 3 4 5 6 // Capitalize <Right > extends Right 判断是否为-type CamelCase <T extends string> = T extends `${infer Left }-${infer Right }` ? Capitalize <Right > extends Right ? `${Left }-${CamelCase <Capitalize <Right >>}` : `${Left }${CamelCase <Capitalize <Right >>}` : T type a = CamelCase <'foo-bar-baz-a'>
Diff Get an Object that is the difference between O & O1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Foo = { name: string age: string } type Bar = { name: string age: string gender: number } type Diff<T, K> = Pick<T & K, Exclude<keyof T, keyof K> | Exclude<keyof K, keyof T> > type a = Diff<Foo, Bar>
AnyOf Implement Python liked any function in the type system. A type takes the Array and returns true if any element of the Array is true. If the Array is empty, return false.
1 2 3 4 5 6 7 8 type FalseUnion = false | '' | 0 | Record <string| number, never> |[]type AnyOf<T extends any []> = T extends [infer F , ...(infer Reset )] ? F extends FalseUnion ? AnyOf <Reset >: true : false type Sample1 = AnyOf <[1 , "" , false , [], {}]>; type Sample2 = AnyOf <[]>;
键重映射的应用
映射类型中使用 as 将key重新映射,中间可以使用各种运算符,返回never表示去除改属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 interface Example { a: string ; b: string | number ; c: () => void ; d: {}; }type ConditionalPick<T, U> = { [K in keyof T as T[K] extends U ? K : never ]: T[K] }type StringKeysOnly = ConditionalPick<Example, string >;type ConditionalDelete<T, U> = { [K in keyof T as Exclude<K, U>]: T[K] }type test = ConditionalDelete<Example, 'a' >;type ConditionalCapitalize<T, U> = { [K in keyof T as K extends U ? `test${Capitalize<string & K>} ` : K]: T[K] }type test1 = ConditionalCapitalize<Example, 'a' >;
实现一个 Split 工具类型 根据给定的分隔符(Delimiter)对包含分隔符的字符串进行切割。可用于定义 String.prototype.split 方法的返回值类型
1 2 3 4 5 6 7 8 type Item = 'semlinker,lolo,kakuqo' ;type Split< T extends string , Delimiter extends string , > = T extends `${infer F} ${Delimiter} ${infer K} ` ? [F, ...Split<K, Delimiter>] : [T] type ElementType = Split<Item, ',' >;
实现一个 ToPath 工具类型 用于把属性访问(. 或 [])路径转换为元组的形式
1 2 3 4 5 6 7 8 9 10 11 type ToPath2<S extends string> = S extends `${infer F }[${infer B }]` ? [F , B ] : [S ]type ToPath<S extends string> = S extends `${infer F }.${infer R }` ? [...ToPath2 <F >, ...ToPath <R >] : [S ] type c = flatten<[[1 ,2 ],3 ]>type a = ToPath <'foo .bar.baz'> type b = ToPath <'foo [0 ].bar[0 ].baz'>
复杂类型转换为基本类型 将类型为字面类型(标签类型)的属性,转换为基本类型
1 2 3 4 5 export type ToPrimitive<T> = T extends object ? T extends (...args: any) => any ? Function : { [K in keyof T]: ToPrimitive<T[K]> }: T extends {valueOf(): infer R} ? R : T
特殊技巧汇总 1、映射类型的键值重新映射 as never 过滤掉不需要的键, 可以很方便的去掉或者指定处理对象中的某些键;修改key为特殊的类型
参考 基础类型中的 readonly 指定类型readonly 高级类型中的 键重映射的应用 去掉某个属性、将某个属性变为大小写或者修改key
2、keyof类型运算符 keyof any 获取 string | number | symbol 用于约束key 参考 基础类型中的 实现 Record 约束必须是一个正常的对象属性
3、条件类型 1、在条件类型内推断,使用infer推断一个不确定的类型 参考 基础类型中的 获取元组长度 条件类型的约束与infer推断length Awaited 高级类型中的 ReturnType 获取函数返回类型 最后一个元素 堆栈操作 PromiseAll Trim Capitalize Replace 追加参数 Length of String Flatten String to union 2、条件类型的推断约束 推断一个确定的类型,然后可以使用该类型的一些属性
获取元组长度 条件类型的约束与infer推断length
3、索引访问 数组类型通过number获取数组元素的联合类型 基础类型中的 元祖转对象 元组转集合
4、数组的一些方法使用 T[0] T[‘length’] 拓展运算符 基础类型中的 获取第一个元素 获取元组长度 Concat Push & Unshif 高级类型中的 Length of String 利用…解除嵌套数组 Flatten
5、模板字面量类型 使用模板字面量进行推断,字符串操作几乎都会使用,Typescript尝试根据结构匹配给定的字符 高级类型中的 Trim Capitalize Length of String String to union
6、调用对象方法获取原始类型 高级类型中的 复杂类型转换为基本类型
部分题目参考来自https://github.com/type-challenges/type-challenges/blob/master/README.zh-CN.md https://juejin.cn/post/7009046640308781063