说明
- 单纯记录刷题的过程,提高一下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: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
答案
# 思路 MyPick<T, K>
1. 类型收窄 K的key必须在T中存在
2. 循环K中的数据 in
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
[2]Readonly内置工具
实现一下内置类型
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
答案
# 思路
1. 使用keyof 获取泛型的所有Key
2. 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 const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
答案
# 思路
1. 类型收窄限制泛型必须是字符串元组
2. 获取as const 字面量类型 所有的值,限定是readonly
3. 通过元组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 + 类型解构 + infer
type 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 4
type 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-error
type 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