说明

image.png
image.png

  • 单纯记录刷题的过程,提高一下Typescript的水平
  • 尽量不用内置的工具,自己实现全部的类型挑战
  • 答案中记录解题思路,把所有的考点整理到前置知识中,方便查阅

    环境准备

    在vscode中安装插件Type challenges 就可以开始刷题了,非常方便

image.png

前置知识

类型

  • 交叉类型 **A & B**
  • 联合类型** A | B**

    关键字

  • never 关键字

  • extends关键字
    • extends 类型收窄
    • extends 判断 一个类型是否包含另一个类型【条件类型】
    • 联合类型的extends 会对每一个类型分别判断【分布式条件类型】
  • in关键字
    • 遍历联合类型
  • keyof关键字
    • 获取一个类型所有的key
  • infer 关键字

    元组

  • **T[number]**获取tuple元组的所有类型,并返回一个联合类型

  • **T["length"]**获取元组的长度

    其他

  • 类型的解构 **[infer First, ...infer Rest]**

  • 数组类型的解构
  • 类型递归

开始刷题

[1] Pick 内置工具

实现一下内置函数Pick

  1. interface Todo {
  2. title: string
  3. description: string
  4. completed: boolean
  5. }
  6. type TodoPreview = MyPick<Todo, 'title' | 'completed'>
  7. const todo: TodoPreview = {
  8. title: 'Clean room',
  9. completed: false,
  10. }

答案

  1. # 思路 MyPick<T, K>
  2. 1. 类型收窄 Kkey必须在T中存在
  3. 2. 循环K中的数据 in
  4. type MyPick<T, K extends keyof T> = {
  5. [P in K]: T[P]
  6. }

[2]Readonly内置工具

实现一下内置类型

  1. interface Todo {
  2. title: string
  3. description: string
  4. }
  5. const todo: MyReadonly<Todo> = {
  6. title: "Hey",
  7. description: "foobar"
  8. }
  9. todo.title = "Hello" // Error: cannot reassign a readonly property
  10. todo.description = "barFoo" // Error: cannot reassign a readonly property

答案

  1. # 思路
  2. 1. 使用keyof 获取泛型的所有Key
  3. 2. in操作符遍历
  4. 3. 在类型映射的时候增加readonly限制
  5. type myReadonly<T> = {
  6. readonly [P in keyof T]: T[P]
  7. }

[3]Tuple to Object

把一个元组转换成对象

  1. const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
  2. type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

答案

image.png

  1. # 思路
  2. 1. 类型收窄限制泛型必须是字符串元组
  3. 2. 获取as const 字面量类型 所有的值,限定是readonly
  4. 3. 通过元组T[number] 获取到元组所有类型的联合类型
  5. 4. 通过in关键字进行mapping取值
  6. type TupleToObject<T extends readonly string[]> = {
  7. [P in T[number]]: P
  8. }

[4]First Of Array

获取数组的第一个元素类型

  1. type arr1 = ['a', 'b', 'c']
  2. type arr2 = [3, 2, 1]
  3. type head1 = First<arr1> // expected to be 'a'
  4. type head2 = First<arr2> // expected to be 3

答案

  1. # 思路
  2. 1. extends 类型收窄限定一定是一个数组类型
  3. 2. extends + 类型解构 + infer
  4. type First<T extends unknown[]> =
  5. T extends [infer First,...infer Rest]? First:never

[5]length of Tuple

获取元组的长度

  1. type tesla = ['tesla', 'model 3', 'model X', 'model Y']
  2. type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
  3. type teslaLength = Length<tesla> // expected 4
  4. type spaceXLength = Length<spaceX> // expected 5

答案

  1. # 思路
  2. 1. extends 限定一下传入的泛型是一个元组
  3. 2.通过元组的T["length"] 获取长度
  4. type Length<T extends readonly unknown[]> = T["length"]

[6]Exclude

实现内置的工具类型

  1. type cases = [
  2. Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, Exclude<'a' | 'b' | 'c', 'a'>>>,
  3. Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, Exclude<'a' | 'b' | 'c', 'a' | 'b'>>>,
  4. Expect<Equal<MyExclude<string | number | (() => void), Function>, Exclude<string | number | (() => void), Function>>>,
  5. ]

答案

  1. # 思路
  2. 1. T目标类型是联合类型
  3. 2. R原始类型也是联合类型
  4. 3. 使用联合类型的extends,对两个联合类型进行分布式条件判断对比 [有点像两个数组循环对比]
  5. type MyExclude<T, U> = T extends U ? never : T;

[7]Awaited

实现一个获取Promise包裹的类型

  1. type X = Promise<string>
  2. type Y = Promise<{ field: number }>
  3. type Z = Promise<Promise<string | number>>
  4. type cases = [
  5. Expect<Equal<MyAwaited<X>, string>>,
  6. Expect<Equal<MyAwaited<Y>, { field: number }>>,
  7. Expect<Equal<MyAwaited<Z>, string | number>>,
  8. ]
  9. // @ts-expect-error
  10. type error = MyAwaited<number>

答案

  1. # 思路
  2. 1. 类型收窄 约束一下必须是Promise包裹的类型
  3. 2. 如果Promise包裹的还是Promise需要使用递归继续取里面的类型
  4. 3. 需要使用infer关键字获取Promise包裹的类型
  5. type MyAwaited<T extends Promise<unknown>> =
  6. T extends Promise<infer R> ? // 判断是不是被Promise包裹
  7. (R extends Promise<unknown> ? MyAwaited<R> : R) // 看包裹的类型是否需要递归处理
  8. : never; // 不是Promise包裹的直接返回错误

[8]If

实现一个工具类型If,接受一个条件C,如果是真返回T,否则返回F,要求C只能是真或者假,T和F可以是任何类型

  1. type A = If<true, 'a', 'b'> // expected to be 'a'
  2. type B = If<false, 'a', 'b'> // expected to be 'b'

答案

  1. # 思路
  2. 1.根据题目要求需要对C进行类型收窄
  3. type If<C extends boolean,T,F> = C extends true?T:F;

[9] Concat

在类型系统中实现javascript的Array.concat方法,这个类型接受两个参数,返回一个数组从左到右的顺序包含输入的两个类型

  1. type cases = [
  2. Expect<Equal<Concat<[], []>, []>>,
  3. Expect<Equal<Concat<[], [1]>, [1]>>,
  4. Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
  5. Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
  6. ]

答案

  1. # 思路
  2. 1. 首先需要类型收窄限定需要concat的都是数组类型
  3. 2. 通过数组类型的解构就可以拿到所有的类型组成的新数组
  4. type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]

[10] Includes

实现javascript的Array.includes方法,此类型接受两个参数,输出是布尔值true或者false

  1. type cases = [
  2. Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
  3. Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
  4. Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
  5. Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
  6. Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
  7. Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
  8. Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
  9. Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
  10. Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
  11. Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
  12. Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
  13. Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
  14. Expect<Equal<Includes<[1], 1 | 2>, false>>,
  15. Expect<Equal<Includes<[1 | 2], 1>, false>>,
  16. Expect<Equal<Includes<[null], undefined>, false>>,
  17. Expect<Equal<Includes<[undefined], null>, false>>,
  18. ]

答案

  1. # 思路
  2. 1. 类型收窄限定必须是数组
  3. 2. 使用infer拿到数组中的每一个元素,递归找是否存在
  4. 3. 这里使用了Equal方法没有自己实现【后面完善】
  5. type Includes<T extends any[], K> =
  6. T extends [infer F, ...infer Rest] ?
  7. Equal<F, K> extends true ?
  8. true
  9. : Includes<Rest, K>
  10. : false

[11]Push

实现泛型版本的Array.push

  1. type cases = [
  2. Expect<Equal<Push<[], 1>, [1]>>,
  3. Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,
  4. Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,
  5. ]

答案

  1. # 思路
  2. 1. 类型收窄限定push的类型T一定是一个数
  3. 2. 类型解构
  4. type Push<T extends unknown[],R> = [...T,R]

[12] Unshift

实现类型版本的Array.unshift

  1. type cases = [
  2. Expect<Equal<Unshift<[], 1>, [1]>>,
  3. Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2]>>,
  4. Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,
  5. ]

答案

  1. # 思路
  2. 1. push的思路基本一致
  3. type Unshift<T extends unknown[],R> = [R,...T]

[13] Parameters

实现内置的Parameters工具类型

  1. const foo = (arg1: string, arg2: number): void => { }
  2. const bar = (arg1: boolean, arg2: { a: 'A' }): void => { }
  3. const baz = (): void => { }
  4. type cases = [
  5. Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  6. Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  7. Expect<Equal<MyParameters<typeof baz>, []>>,
  8. ]

答案

  1. # 思路
  2. 1. js的函数参数可以通过args类数组获得
  3. 2. infer+ extends 直接获取函数的参数
  4. type MyParameters<T> = T extends (...args: infer R) => unknown ? R : never