https://:.zhihu.com/p/137745695
https://zhuanlan.zhihu.com/p/52662645

一、Easy

二、Medium

1.获取函数返回类型 ts 一些内置工具泛型的实现 - 图7 ts 一些内置工具泛型的实现 - 图8 ts 一些内置工具泛型的实现 - 图9

不使用 ReturnType 实现 TypeScript 的 ReturnType 泛型。
例如:

  1. const fn = (v: boolean) => {
  2. if (v)
  3. return 1
  4. else
  5. return 2
  6. }
  7. type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"

使用 infer获取返回值的类型

  1. // 返回范型参数T的返回值类型
  2. type MyReturnType<T extends (args: any) => any > = T extends (...args: any) => infer R ? R : any

2.实现 Omit

不使用 Omit 实现 TypeScript 的 Omit 泛型。
Omit 会创建一个省略 K 中字段的 T 对象。
例如:

  1. interface Todo {
  2. title: string
  3. description: string
  4. completed: boolean
  5. }
  6. type TodoPreview = MyOmit<Todo, 'description' | 'title'>
  7. const todo: TodoPreview = {
  8. completed: false,
  9. }
  1. //1、第一种 Exclude 获取 T 类型中有, K类型中没有的成员,Pick 返回 T类型中的存在的这几个成员
  2. type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  3. //2、第二种
  4. type MyOmit<T, K extends keyof any> = {
  5. [key in Exclude<keyof T, K>]: T[key]
  6. }
  7. //3、第三种
  8. type MyOmit<T, K extends keyof any> = {
  9. [key in keyof T as key extends K ? never : key]: T[key]
  10. }

3.Readonly2

实现一个通用MyReadonly2,它带有两种类型的参数T和K。
K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly一样。
例如:

  1. interface Todo {
  2. title: string
  3. description: string
  4. completed: boolean
  5. }
  6. const todo: MyReadonly2<Todo, 'title' | 'description'> = {
  7. title: "Hey",
  8. description: "foobar",
  9. completed: false,
  10. }
  11. todo.title = "Hello" // Error: cannot reassign a readonly property
  12. todo.description = "barFoo" // Error: cannot reassign a readonly property
  13. todo.completed = true // OK
  1. // 指定的key加上readonly属性,其他的原样返回
  2. type MyReadonly2<T, K extends keyof T = keyof T> = {
  3. readonly [P in K]: T[P]
  4. } & {
  5. [S in Exclude<keyof T, K> : T[S]]
  6. }
  7. // K extends keyof T = keyof T 的理解:默认K 的值为泛型T的所有key,题目的要求是没有提供K的情况下全部设置readonly

4.深度Readonly

实现一个通用的DeepReadonly,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
例如:

  1. type X = {
  2. x: {
  3. a: 1
  4. b: 'hi'
  5. }
  6. y: 'hey'
  7. }
  8. type Expected = {
  9. readonly x: {
  10. readonly a: 1
  11. readonly b: 'hi'
  12. }
  13. readonly y: 'hey'
  14. }
  15. type Todo = DeepReadonly<X> // should be same as `Expected`

实现如下:

  1. type DeepReadonly<X>={
  2. readonly [P in keyof X]: T[P] extends object
  3. ? T[P] extends Function
  4. ? T[P]
  5. : DeepReadonly<T[P]>
  6. : T[P]
  7. }

5.元组转合集(TODO)

实现泛型TupleToUnion,它返回元组所有值的合集。
例如:

  1. type Arr = ['1', '2', '3']
  2. type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'

实现

  1. // 相当于遍历数组下标 number
  2. type TupleToUnion<T extends any[]> = T[number]
  3. // 递归,符合数组格式,取出数组第一个和第二个组合成 First | Second,递归拼凑第一个到最后一个。 如果数组只有一个时,返回T[0]
  4. type TupleToUnion<T extends any[]> = T extends [infer First, infer Second, ...infer Rest] ? TupleToUnion<[First | Second, ...Rest]>

6.可串联构造器(TODO)
7.最后一个元素

实现一个通用Last,它接受一个数组T并返回其最后一个元素的类型。
例如

  1. type arr1 = ['a', 'b', 'c']
  2. type arr2 = [3, 2, 1]
  3. type tail1 = Last<arr1> // expected to be 'c'
  4. type tail2 = Last<arr2> // expected to be 1

实现

  1. // ...展开运算符 获取除最后一个之前的元素, L 是最后一个
  2. type Last<T extends any[]> = T extends [...infer P, infer L] ? L : nerver

8.出堆

实现一个通用Pop,它接受一个数组T并返回一个没有最后一个元素的数组。
例如

  1. type arr1 = ['a', 'b', 'c', 'd']
  2. type arr2 = [3, 2, 1]
  3. type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
  4. type re2 = Pop<arr2> // expected to be [3, 2]

实现

  1. type pop<T extends any[]> = T extends [...infer P, infer L] ? P : nerver

9.Promise.all

键入函数PromiseAll,它接受PromiseLike对象数组,返回值应为Promise,其中T是解析的结果数组。

  1. const promise1 = Promise.resolve(3);
  2. const promise2 = 42;
  3. const promise3 = new Promise<string>((resolve, reject) => {
  4. setTimeout(resolve, 100, 'foo');
  5. });
  6. // expected to be `Promise<[number, 42, string]>`
  7. const p = Promise.all([promise1, promise2, promise3] as const)

实现

  1. declare function PromiseAll<T extends unknown[]>(values: readonly [...T]): Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>
  2. // 另一种实现方式
  3. type PromiseType<T> = T extends Promise<infer PT> ? PT: T
  4. type PromisesType<T extends readonly any[]> = Promise<T extends readonly [infer First, ...infer Rest]
  5. ? [PromiseType<First>, ...PromisesType<Rest>]
  6. : []>
  7. /*
  8. type PromisesType<T extends readonly any[]> = Promise<{
  9. [P in keyof T]: PromiseType<T[P]>
  10. }>
  11. */
  12. declare const PromiseAll: <T extends readonly any[]>(promises: T) => PromisesType<T>

10.Type Lookup

有时,您可能希望根据其属性在并集中查找类型。
在此挑战中,我们想通过在联合Cat | Dog中搜索公共type字段来获取相应的类型。换句话说,在以下示例中,我们期望LookUp获得Dog,LookUp获得Cat。

  1. interface Cat {
  2. type: 'cat'
  3. breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
  4. }
  5. interface Dog {
  6. type: 'dog'
  7. breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  8. color: 'brown' | 'white' | 'black'
  9. }
  10. type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`

实现

  1. type LookUp<U extends {type: string}, K> = U extends { type: K } ? U : nerver
  2. type LookUp<U extends {type : string}, T extends U['type']> = U extends { type : T} ? U :never;

联合类型一个U就可以搞定,真是没想到。

11.Trim Left

实现 TrimLeft ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
例如

  1. type trimed = TrimLeft<' Hello World '> // 应推导出 'Hello World '

实现

  1. type TrimLeft<T extends string> = T extends `${' ' | '\n' | '\t'}${infer R}` ? TrimLeft<R> : S

12.Trim Right

实现 TrimRight ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串结尾的空白字符串。
例如

  1. type Trimed = TrimRight<' Hello World '> // 应推导出 ' Hello World'

实现

  1. type TrimRight<S extends string> = S extends `${infer L}${' ' | '\n' | '\t'}` ? TrimRight<L> : S

13.Trim

实现Trim,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。
例如

  1. type trimed = Trim<' Hello World '> // expected to be 'Hello World'

实现

  1. type Trim<S extends string> = S extends
  2. `${' ' | '\n' | '\t'}${infer Rest}${' ' | '\n' | '\t'}` |
  3. `${' ' | '\n' | '\t'}${infer Rest}` |
  4. `${infer Rest}${' ' | '\n' | '\t'}` ? Trim<Rest> : S

14.Capitalize

实现 Capitalize 它将字符串的第一个字母转换为大写,其余字母保持原样。
例如

  1. type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'

实现

  1. // 解法一
  2. type Capitalize<S extends string> = S extends `${infer F}${infer R}` ? `${Uppercase<F>}${R}` : S
  3. // 解法二
  4. type Alphabet = {
  5. a: 'A'
  6. b: 'B'
  7. c: 'C'
  8. d: 'D'
  9. e: 'E'
  10. f: 'F'
  11. g: 'G'
  12. h: 'H'
  13. i: 'I'
  14. j: 'J'
  15. k: 'K'
  16. l: 'L'
  17. m: 'M'
  18. n: 'N'
  19. o: 'O'
  20. p: 'P'
  21. q: 'Q'
  22. r: 'R'
  23. s: 'S'
  24. t: 'T'
  25. u: 'U'
  26. v: 'V'
  27. w: 'W'
  28. x: 'X'
  29. y: 'Y'
  30. z: 'Z'
  31. }
  32. type Capitalize<S extends string> = S extends `${infer F}${infer R}` ?
  33. F extends keyof Alphabet ?
  34. `${Alphabet[F]}${R}` :
  35. S:
  36. S
  37. type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'

infer F 获取到了第一个字符,infer R 获取到的是除第一个元素之外的其他字符

15.Replace

实现 Replace 将字符串 S 中的第一个子字符串 From 替换为 To 。
例如

  1. type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'

实现

  1. type Replace<S extends string, From extends string, To extends string> =
  2. From extends '' ? S
  3. : S extends `${infer Front}${infer From}${infer Behind}`
  4. ? `${Front}${To}${Behind}` : S

16.ReplaceAll

infer 没搞懂

17.追加参数

实现一个泛型 AppendArgument,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

  1. type Fn = (a: number, b: string) => number
  2. type Result = AppendArgument<Fn, boolean>
  3. // 期望是 (a: number, b: string, x: boolean) => number

这道题的关键是拿到 Fn的参数和返回值类型,然后赋值给一个新的函数
实现

  1. type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer R ? (...args: [...Args, A]) => R : never

18.Permutation

实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

  1. type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
  1. // [T] extends [never] -> prevent it from being distributive
  2. // T extends keyof any -> declare T is either string, number, or symbol to traverse
  3. // [TT in T] -> 遍历 T
  4. // [TT, ...Permutation<Exclude<T, TT>>] -> Permutate the rest element
  5. // {}[T] -> 将对象变成数组的联合
  6. type Permutation<T extends keyof any> = [T] extends [never] ?
  7. [] :
  8. {
  9. [P in T]: [P, ...Permutation<Exclude<T, P>>]
  10. }[T]
  11. type perm = Permutation<'A' | 'B' | 'C'>

19.计算字符串的长度

计算字符串的长度,类似于 String#length 。

  1. // 字符串模板中两个 infer 相邻,第一个 infer 只会推断出单个字符,这有助于一些递归操作,比如 ${infer L}${infer R} 去推断 abcd,L 会推断为 a,而 R 会推断为 bcd。
  2. type LengthOfString<S extends string, N extends any[] = []> = S extends `${infer L}${infer R}` ? LengthOfString<R,[...N, L]> : N['length']

三、Hard