image.png

知识点梳理

  • 类型收窄 extends
  • 类型收窄也可以给默认值
    • K extends keyof T = keyof T // 如果K不存在就把keyof T当默认值给K
  • in操作符进行mapping映射
  • as进行remapping重映射 ```typescript type Remap = {

}

  1. - 元组T通过T[number]可以获取元组中所有类型的联合类型union
  2. ```typescript
  3. const Gender = ["Male","Female"] as const
  4. type genderUnion = Gender[number] // Male | Female
  • 判断一个字符串字面量是否含有另一个字符串字面量 ```typescript // 模板字符串我们能够判断一个字符串中是否存在某一个其他字符

type ReplaceTest = “ABCD” extends ${infer Prefix}${"BC"}${infer Suffix} ? ${Prefix}${Suffix} : never; // AD ts中没法通过正则匹配,只能通过模板字符串

  1. - 通过 ...arg infer 相互配合能够用一个数组提取出函数的所有入参
  2. ```typescript
  3. // 获取所有的参数 [ a:number,b:string ]
  4. type GetArgs = ((a: number, b: string) => number) extends (...arg:infer Args)=>infer Result ? Args : never
  5. // 获取返回值
  6. // number
  7. type GetResult = ((a: number, b: string) => number) extends (...arg:infer Args)=>infer Result ? Result : never

刷题开始

[1] ReturnType

实现内置的ReturnType类型,获取函数的返回值

  1. type cases = [
  2. Expect<Equal<string, MyReturnType<() => string>>>,
  3. Expect<Equal<123, MyReturnType<() => 123>>>,
  4. Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
  5. Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
  6. Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,
  7. Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
  8. Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
  9. ]
  10. type ComplexObject = {
  11. a: [12, 'foo']
  12. bar: 'hello'
  13. prev(): number
  14. }
  15. const fn = (v: boolean) => v ? 1 : 2
  16. const fn1 = (v: boolean, w: any) => v ? 1 : 2

答案

  1. # 思路
  2. 1. 首先类型收窄限定泛型参数必须是一个函数 extends
  3. 2. 使用infer获取函数的返回值
  4. type MyReturnType<T extends Function> = T extends (...args: any[]) => infer R ? R : never;

[2] Omit

实现内置的Omit方法

  1. type cases = [
  2. Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,
  3. Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,
  4. ]
  5. // @ts-expect-error
  6. type error = MyOmit<Todo, 'description' | 'invalid'>
  7. interface Todo {
  8. title: string
  9. description: string
  10. completed: boolean
  11. }
  12. interface Expected1 {
  13. title: string
  14. completed: boolean
  15. }
  16. interface Expected2 {
  17. title: string
  18. }

答案

  1. # 思路
  2. 1. 类型收窄,限定需要omit的联合类型属性R肯定在keyof T
  3. 2. 需要在使用in操作符mapping的时候使用as remapping把在R中的属性去掉
  4. type MyOmit<T, R extends keyof T> = {
  5. [P in keyof T as P extends R ? never : P]: T[P]
  6. }

[3] Readonly2

实现一个MyReadonly接受两个泛型参数T,K,K是T中需要设置成readonly的属性集,如果K没有则T中所有的属性都是readonly,等价于MyReadonly

  1. // ============= Test Cases =============
  2. import type { Alike, Expect } from './test-utils'
  3. type cases = [
  4. Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
  5. Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
  6. Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
  7. ]
  8. interface Todo1 {
  9. title: string
  10. description?: string
  11. completed: boolean
  12. }
  13. interface Todo2 {
  14. readonly title: string
  15. description?: string
  16. completed: boolean
  17. }
  18. interface Expected {
  19. readonly title: string
  20. readonly description?: string
  21. completed: boolean
  22. }
  23. // ============= Your Code Here =============
  24. # 思路
  25. 1. 类型收窄限定K必须在T里面,如果K不存在就给默认值keyof T
  26. 2. 如果在K里面的通过in遍历加上readonly
  27. 3. 如果不在k里面的通过交叉类型保持不变
  28. // 上面已经实现过的MyOmit
  29. type MyOmit<T, R extends keyof T> = {
  30. // 通过as来remapping
  31. [P in keyof T as P extends R ? never : P]: T[P];
  32. };
  33. type MyReadonly2<T, K extends keyof T = keyof T> = MyOmit<T, K> & {
  34. readonly [P in K]: T[P];
  35. };

[4] deep readonly

所有属性和子属性递归readonly

  1. type cases = [
  2. Expect<Equal<DeepReadonly<X>, Expected>>,
  3. ]
  4. type X = {
  5. a: () => 22
  6. b: string
  7. c: {
  8. d: boolean
  9. e: {
  10. g: {
  11. h: {
  12. i: true
  13. j: 'string'
  14. }
  15. k: 'hello'
  16. }
  17. l: [
  18. 'hi',
  19. {
  20. m: ['hey']
  21. },
  22. ]
  23. }
  24. }
  25. }
  26. type Expected = {
  27. readonly a: () => 22
  28. readonly b: string
  29. readonly c: {
  30. readonly d: boolean
  31. readonly e: {
  32. readonly g: {
  33. readonly h: {
  34. readonly i: true
  35. readonly j: 'string'
  36. }
  37. readonly k: 'hello'
  38. }
  39. readonly l: readonly [
  40. 'hi',
  41. {
  42. readonly m: readonly ['hey']
  43. },
  44. ]
  45. }
  46. }
  47. }

答案

  1. # 思路
  2. 1. 首先肯定要用到递归,处理子类型中的readonly
  3. 2. 通过in遍历传入泛型的所有属性
  4. 3. 通过extends进行类型收窄判断,基本类型直接返回类型,除函数外的对象类型递归处理
  5. // 需要注意 type isObject = Function extends Object?true:false // true
  6. // 函数也是对象类型,但是上面例子函数不需要递归处理
  7. type ExcludeType = string | number | symbol | Function
  8. type DeepReadonly<T> = {
  9. readonly [P in keyof T]: T[P] extends ExcludeType ? T[P] : DeepReadonly<T[P]>
  10. }

[5]Tuple to Union Type Challengs 进阶题目 - 图10 Type Challengs 进阶题目 - 图11 Type Challengs 进阶题目 - 图12 Type Challengs 进阶题目 - 图13

把一个**[Tuple]元组类型** 转换成 **Union联合类型**

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

答案

  1. # 思路:通过 number 直接可以将元组类型转换为联合类型
  2. type TupleToUnion<Arr extends unknown[]> = Arr[number]

[6]Chainable options

实现一个链式options类型,option进行配置类型,通过get获取所有的配置类型

  1. declare const a: Chainable
  2. const result1 = a
  3. .option('foo', 123)
  4. .option('bar', { value: 'Hello World' })
  5. .option('name', 'type-challenges')
  6. .get()
  7. const result2 = a
  8. .option('name', 'another name')
  9. // @ts-expect-error
  10. .option('name', 'last name')
  11. .get()
  12. type cases = [
  13. Expect<Alike<typeof result1, Expected1>>,
  14. Expect<Alike<typeof result2, Expected2>>,
  15. ]
  16. type Expected1 = {
  17. foo: number
  18. bar: {
  19. value: string
  20. }
  21. name: string
  22. }
  23. type Expected2 = {
  24. name: string
  25. }

答案:

  1. # 思路
  2. 1. 首先约束一下结果Result必须是一个对象,给一个默认值{}
  3. 2. option接受两个泛型参数keyvalue,通过K extends keyof Result判断key不能重复出现在result
  4. 3. 因为是链式,所以option的返回结果也是一个Chainable,但是此时的泛型参数已经是Result结果+新传入的option的交集了
  5. type Chainable<Result extends Record<string, unknown> = {}> = {
  6. option: <K extends string, V>(key: K extends keyof Result ? never : K, value: V) => Chainable<Result & {
  7. [P in K]: V
  8. }>
  9. get: () => Result
  10. }

[7] last of array

实现一个返回数组最后一个类型的工具

  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. # 思路
  2. 1. 通过extends条件类型 + infer 获取数组的最后一位
  3. type LastOfArray<T> = T extends [... infer Rest, infer Last] ? Last: never

[8] Pop

实现一个类似js的pop去掉最后一位的一个类型的工具

  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. # 思路
  2. 1. 通过extends条件类型 + infer 获取数组的最后一位
  3. type Pop<T> = T extends [... infer Rest, infer Last] ? Rest: never

[9] PromiseAll

实现一个类似js的Pormise.all

  1. const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
  2. const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
  3. const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
  4. type cases = [
  5. Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
  6. Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
  7. Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
  8. ]

答案

  1. # 思路
  2. 1. 类型收窄限定T必须是一个数组
  3. 2. 通过类型解构拿到数组中所有的参数类型T
  4. 3. 返回值必须拿Promise包裹
  5. 4. 包裹的属性通过in遍历一下,如果还是一个promise的话继续通过infer取出里面的类型,否则直接返回类型
  6. 5. 个人感觉本地只有两层promise,实际上应该通过递归处理
  7. // ============= Your Code Here =============
  8. declare function PromiseAll<T extends any[] = any[]>(
  9. values: readonly [...T]
  10. ): Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;

[10] Type lookup

从联合类型中通过type属性查找到目标类型

  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 MyDogType = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`

答案

  1. # 思路
  2. 1. 分布式条件判断extends + infer
  3. type LookUp<U, T extends string> = U extends {
  4. type: infer R
  5. } ? R extends T ? U : never : never

[11] Trim left

移除字符串左侧的空白

  1. type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
  2. type cases = [
  3. Expect<Equal<TrimLeft<'str'>, 'str'>>,
  4. Expect<Equal<TrimLeft<' str'>, 'str'>>,
  5. Expect<Equal<TrimLeft<' str'>, 'str'>>,
  6. Expect<Equal<TrimLeft<' str '>, 'str '>>,
  7. Expect<Equal<TrimLeft<' \n\t foo bar '>, 'foo bar '>>,
  8. Expect<Equal<TrimLeft<''>, ''>>,
  9. Expect<Equal<TrimLeft<' \n\t'>, ''>>,
  10. ]

答案

  1. # 思路
  2. 1. extends 进行模式匹配,然后递归去空格
  3. // 本来写的答案是这个,没有考虑转义字符
  4. type TrimLeft<S extends string> = S extends ` ${infer Rest}`
  5. ? TrimLeft<Rest>
  6. : S;
  7. // case中包含转移自付
  8. type TrimLeft<S extends string> = S extends `${" " | "\n" | "\t"}${infer Rest}`
  9. ? TrimLeft<Rest>
  10. : S;

[12] Trim right

移除字符串右侧的空白

  1. type trimed = TrimRight<' Hello World '> // expected to be 'Hello World '
  2. type cases = [
  3. Expect<Equal<TrimRight<'str'>, 'str'>>,
  4. Expect<Equal<TrimRight<'str '>, 'str'>>,
  5. Expect<Equal<TrimRight<'str '>, 'str'>>,
  6. Expect<Equal<TrimRight<' str '>, ' str'>>,
  7. Expect<Equal<TrimRight<'\n\t foo bar '>, ' foo bar'>>,
  8. Expect<Equal<TrimRight<''>, ''>>,
  9. Expect<Equal<TrimRight<'\n\t '>, ''>>,
  10. ]

答案

  1. # 思路
  2. 1. 通过extends条件类型 + infer 获取数组的最后一位
  3. type TrimRight<S extends string> = S extends `${infer Rest}${" " | "\n" | "\t"}`
  4. ? TrimRight<Rest>
  5. : S;

[13] Trim

移除字符串两侧的空白

  1. type cases = [
  2. Expect<Equal<Trim<'str'>, 'str'>>,
  3. Expect<Equal<Trim<' str'>, 'str'>>,
  4. Expect<Equal<Trim<' str'>, 'str'>>,
  5. Expect<Equal<Trim<'str '>, 'str'>>,
  6. Expect<Equal<Trim<' str '>, 'str'>>,
  7. Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>,
  8. Expect<Equal<Trim<''>, ''>>,
  9. Expect<Equal<Trim<' \n\t '>, ''>>,
  10. ]

答案

  1. # 思路
  2. 1. 左侧和右侧实现的方法进行组合
  3. type Trim<S extends string> = TrimLeft<TrimRight<S>>;

[15] Capitalize

首字母大写

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

答案

  1. # 思路
  2. 1. 通过extends条件类型 + infer
  3. type MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}`?`${Uppercase<F>}${Rest}`:S

[16] Replace

实现类似js的replace

  1. type cases = [
  2. Expect<Equal<Replace<"foobar", "bar", "foo">, "foofoo">>,
  3. Expect<Equal<Replace<"foobarbar", "bar", "foo">, "foofoobar">>,
  4. Expect<Equal<Replace<"foobarbar", "", "foo">, "foobarbar">>,
  5. Expect<Equal<Replace<"foobarbar", "bar", "">, "foobar">>,
  6. Expect<Equal<Replace<"foobarbar", "bra", "foo">, "foobarbar">>,
  7. Expect<Equal<Replace<"", "", "">, "">>
  8. ];

答案

  1. # 思路
  2. 1. 通过extends 类型收窄 限定都是字符串
  3. 2. 通过模板字符串进行匹配
  4. 3. 根据要求把目标字符串进行替换
  5. type Replace<
  6. S extends string,
  7. From extends string,
  8. To extends string
  9. > = S extends `${infer Prefix}${From}${infer Suffix}`
  10. ? From extends ""
  11. ? S
  12. : `${Prefix}${To}${Suffix}`
  13. : S;

[17] ReplaceAll

替换全部的From为To

  1. type cases = [
  2. Expect<Equal<ReplaceAll<"foobar", "bar", "foo">, "foofoo">>,
  3. Expect<Equal<ReplaceAll<"foobar", "bag", "foo">, "foobar">>,
  4. Expect<Equal<ReplaceAll<"foobarbar", "bar", "foo">, "foofoofoo">>,
  5. Expect<Equal<ReplaceAll<"t y p e s", " ", "">, "types">>,
  6. Expect<Equal<ReplaceAll<"foobarbar", "", "foo">, "foobarbar">>,
  7. Expect<Equal<ReplaceAll<"barfoo", "bar", "foo">, "foofoo">>,
  8. Expect<Equal<ReplaceAll<"foobarfoobar", "ob", "b">, "fobarfobar">>,
  9. Expect<Equal<ReplaceAll<"foboorfoboar", "bo", "b">, "foborfobar">>,
  10. Expect<Equal<ReplaceAll<"", "", "">, "">>
  11. ];

答案

  1. # 思路
  2. 1. 上面已经实现了replace,替换第一个
  3. 2. 替换全部,也就是对目标以外的prefixsuffix递归调用一下replace方法
  4. type ReplaceAll<
  5. S extends string,
  6. From extends string,
  7. To extends string
  8. > = S extends `${infer Prefix}${From}${infer Suffix}`
  9. ? From extends ""
  10. ? S
  11. : `${ReplaceAll<Prefix, From, To>}${To}${ReplaceAll<Suffix, From, To>}`
  12. : S;

Append Argument

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

  1. type Case1 = AppendArgument<(a: number, b: string) => number, boolean>
  2. type Result1 = (a: number, b: string, x: boolean) => number
  3. type Case2 = AppendArgument<() => void, undefined>
  4. type Result2 = (x: undefined) => void
  5. type cases = [
  6. Expect<Equal<Case1, Result1>>,
  7. Expect<Equal<Case2, Result2>>,
  8. ]

答案

  1. # 思路
  2. 1. 通过类型解构+infer先获取原函数的参数和返回值
  3. 2. 再将新的参数拼接到原始参数之后,返回默认的返回值
  4. type AppendArgument<Fn extends Function, A> = Fn extends (
  5. ...args: infer arguments
  6. ) => infer Result
  7. ? (...args: [...arguments, A]) => Result
  8. : never;