说明


- 单纯记录刷题的过程,提高一下Typescript的水平
- 尽量不用内置的工具,自己实现全部的类型挑战
- 答案中记录解题思路,把所有的考点整理到前置知识中,方便查阅
环境准备
在vscode中安装插件Type challenges 就可以开始刷题了,非常方便
前置知识
类型
- 交叉类型
**A & B** -
关键字
never 关键字
- extends关键字
- extends 类型收窄
- extends 判断 一个类型是否包含另一个类型【条件类型】
- 联合类型的extends 会对每一个类型分别判断【分布式条件类型】
- in关键字
- 遍历联合类型
- keyof关键字
- 获取一个类型所有的key
-
元组
**T[number]**获取tuple元组的所有类型,并返回一个联合类型-
其他
类型的解构
**[infer First, ...infer Rest]**- 数组类型的解构
- 类型递归
开始刷题
[1] Pick 内置工具
实现一下内置函数Pick
interface Todo {title: stringdescription: stringcompleted: boolean}type TodoPreview = MyPick<Todo, 'title' | 'completed'>const todo: TodoPreview = {title: 'Clean room',completed: false,}
答案
# 思路 MyPick<T, K>1. 类型收窄 K的key必须在T中存在2. 循环K中的数据 intype MyPick<T, K extends keyof T> = {[P in K]: T[P]}
[2]Readonly内置工具
实现一下内置类型
interface Todo {title: stringdescription: string}const todo: MyReadonly<Todo> = {title: "Hey",description: "foobar"}todo.title = "Hello" // Error: cannot reassign a readonly propertytodo.description = "barFoo" // Error: cannot reassign a readonly property
答案
# 思路1. 使用keyof 获取泛型的所有Key2. in操作符遍历3. 在类型映射的时候增加readonly限制type myReadonly<T> = {readonly [P in keyof T]: T[P]}
[3]Tuple to Object
把一个元组转换成对象
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as consttype result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
答案

# 思路1. 类型收窄限制泛型必须是字符串元组2. 获取as const 字面量类型 所有的值,限定是readonly3. 通过元组T[number] 获取到元组所有类型的联合类型4. 通过in关键字进行mapping取值type TupleToObject<T extends readonly string[]> = {[P in T[number]]: P}
[4]First Of Array
获取数组的第一个元素类型
type arr1 = ['a', 'b', 'c']type arr2 = [3, 2, 1]type head1 = First<arr1> // expected to be 'a'type head2 = First<arr2> // expected to be 3
答案
# 思路1. extends 类型收窄限定一定是一个数组类型2. extends + 类型解构 + infertype First<T extends unknown[]> =T extends [infer First,...infer Rest]? First:never
[5]length of Tuple
获取元组的长度
type tesla = ['tesla', 'model 3', 'model X', 'model Y']type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']type teslaLength = Length<tesla> // expected 4type spaceXLength = Length<spaceX> // expected 5
答案
# 思路1. extends 限定一下传入的泛型是一个元组2.通过元组的T["length"] 获取长度type Length<T extends readonly unknown[]> = T["length"]
[6]Exclude
实现内置的工具类型
type cases = [Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, Exclude<'a' | 'b' | 'c', 'a'>>>,Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, Exclude<'a' | 'b' | 'c', 'a' | 'b'>>>,Expect<Equal<MyExclude<string | number | (() => void), Function>, Exclude<string | number | (() => void), Function>>>,]
答案
# 思路1. T目标类型是联合类型2. R原始类型也是联合类型3. 使用联合类型的extends,对两个联合类型进行分布式条件判断对比 [有点像两个数组循环对比]type MyExclude<T, U> = T extends U ? never : T;
[7]Awaited
实现一个获取Promise包裹的类型
type X = Promise<string>type Y = Promise<{ field: number }>type Z = Promise<Promise<string | number>>type cases = [Expect<Equal<MyAwaited<X>, string>>,Expect<Equal<MyAwaited<Y>, { field: number }>>,Expect<Equal<MyAwaited<Z>, string | number>>,]// @ts-expect-errortype error = MyAwaited<number>
答案
# 思路1. 类型收窄 约束一下必须是Promise包裹的类型2. 如果Promise包裹的还是Promise需要使用递归继续取里面的类型3. 需要使用infer关键字获取Promise包裹的类型type MyAwaited<T extends Promise<unknown>> =T extends Promise<infer R> ? // 判断是不是被Promise包裹(R extends Promise<unknown> ? MyAwaited<R> : R) // 看包裹的类型是否需要递归处理: never; // 不是Promise包裹的直接返回错误
[8]If
实现一个工具类型If,接受一个条件C,如果是真返回T,否则返回F,要求C只能是真或者假,T和F可以是任何类型
type A = If<true, 'a', 'b'> // expected to be 'a'type B = If<false, 'a', 'b'> // expected to be 'b'
答案
# 思路1.根据题目要求需要对C进行类型收窄type If<C extends boolean,T,F> = C extends true?T:F;
[9] Concat
在类型系统中实现javascript的Array.concat方法,这个类型接受两个参数,返回一个数组从左到右的顺序包含输入的两个类型
type cases = [Expect<Equal<Concat<[], []>, []>>,Expect<Equal<Concat<[], [1]>, [1]>>,Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,]
答案
# 思路1. 首先需要类型收窄限定需要concat的都是数组类型2. 通过数组类型的解构就可以拿到所有的类型组成的新数组type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
[10] Includes
实现javascript的Array.includes方法,此类型接受两个参数,输出是布尔值true或者false
type cases = [Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,Expect<Equal<Includes<[1, 2, 3], 2>, true>>,Expect<Equal<Includes<[1, 2, 3], 1>, true>>,Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,Expect<Equal<Includes<[1], 1 | 2>, false>>,Expect<Equal<Includes<[1 | 2], 1>, false>>,Expect<Equal<Includes<[null], undefined>, false>>,Expect<Equal<Includes<[undefined], null>, false>>,]
答案
# 思路1. 类型收窄限定必须是数组2. 使用infer拿到数组中的每一个元素,递归找是否存在3. 这里使用了Equal方法没有自己实现【后面完善】type Includes<T extends any[], K> =T extends [infer F, ...infer Rest] ?Equal<F, K> extends true ?true: Includes<Rest, K>: false
[11]Push
实现泛型版本的Array.push
type cases = [Expect<Equal<Push<[], 1>, [1]>>,Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,]
答案
# 思路1. 类型收窄限定push的类型T一定是一个数2. 类型解构type Push<T extends unknown[],R> = [...T,R]
[12] Unshift
实现类型版本的Array.unshift
type cases = [Expect<Equal<Unshift<[], 1>, [1]>>,Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2]>>,Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,]
答案
# 思路1. 跟push的思路基本一致type Unshift<T extends unknown[],R> = [R,...T]
[13] Parameters
实现内置的Parameters工具类型
const foo = (arg1: string, arg2: number): void => { }const bar = (arg1: boolean, arg2: { a: 'A' }): void => { }const baz = (): void => { }type cases = [Expect<Equal<MyParameters<typeof foo>, [string, number]>>,Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,Expect<Equal<MyParameters<typeof baz>, []>>,]
答案
# 思路1. js的函数参数可以通过args类数组获得2. infer+ extends 直接获取函数的参数type MyParameters<T> = T extends (...args: infer R) => unknown ? R : never

