之前深信服前端一面的时候,面试官问我会 TypeScript
吗?想到我之前说 Vue3
原理时顺便提了一口 Vue3
是基于 TypeScript
写的,我既然懂 Vue3
原理,那说不会一点 TypeScript
那实在是有点说不过去
结果就是面试官直接让我实现一个 Pick
,直接戳穿了一个只用过 interface
、type
和泛型的 ts
小白,我虽然背过很多八股文,但很明显没背过 ts
的类型实现,而且只用过一两次 Pick
,所以今天就借这篇文章写一写 Pick
和 Omit
的实现
为什么还要写 Omit
,因为面试官说我不会写 Pick
可以写一个 Omit
,但我两个都不会 /(ㄒoㄒ)/~~
type-challenges
如果你已经了解过关于 type-challenges
和其对应测试方法,可以跳过本章节,直接阅读Pick 章节部分
想要去试试水 ts
类型体操可以去 type-challenges/type-challenges 这个库,里面有很多奇奇怪怪的类型,不常见的那种
本项目意在于让你更好的了解 TS 的类型系统,编写你自己的类型工具,或者只是单纯的享受挑战的乐趣!我们同时希望可以建立一个社区,在这里你可以提出你在实际环境中遇到的问题,或者帮助他人解答疑惑 - 这些问题也可能被选中成为题库的一部分!
那么问题来了,Pick
和 Omit
在不在里面呢?
在,而且是第 3
道和第 4
道题目,悔恨没有面试前看过这个库
在线测试
当我们选择题目之后,会有一个在线测试的链接
测试的目的是让框住部分的异常提示消失
但 Playground
的运行是不会有任何结果的,我写这个挑战的时候曾经一度以为是运行得到是否正确,但我一直没有运行成功
由于它里面有提供 Expect
等类型,我仍然怀疑是可以运行得到结果的,留个悬念,等我弄清楚了再来更新
Pick
题目描述
实现 TS 内置的 Pick<T, K>
,但不可以使用它。
从类型 T
中选择出属性 K
,构造成一个新的类型。
例如:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
测试用例
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
// @ts-expect-error
MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]
interface Todo {
title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
}
interface Expected2 {
title: string
completed: boolean
}
题目模板
/* _____________ 你的代码 _____________ */
type MyPick<T, K> = any
解答
Pick
就是挑选的意思,描述里面就是挑了 title
和 completed
两个属性,那么在 ts
的层面需要如何实现呢?
首先你得保证 title
和 completed
是 Todo
里面的属性,不是的话还挑什么呢?
对于这部分可以使用 keyof
,其实 keyof
在 JS
中也有个很像的 API
,就是 Object.keys()
,Object.keys
的作用就是返回对象的 key
(键) 列表,那么你应该知道 keyof
是干什么的了,就是用于获取某种类型的所有键,其返回类型是联合类型,它是 ts
的一个操作符
因此第一步实现如下,表示 K
是 T
类型中的键
type MyPick<T, K extends keyof T> = any
回到描述里面的 TodoPreview
,其实它的类型如下
interface TodoPreview {
title: string
completed: boolean
}
TodoPreview
其实就是 MyPick
返回的,换言之
type MyPick<Todo, 'title' | 'completed'> = {
title: string
completed: boolean
}
因此实现的思路就是需要在 MyPick
实现中补充一个泛型表示原 Todo
的键和对应类型,即如下
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
这里补充一下 ts
泛型的概念,ts
的泛型可以用来表示未确定的类型,同时在泛型之上添加约束,比如 K extends keyof T
即表示 T
所有键里面的任意一个
而 [P in K]
里面的 in
就不是这个意思了,in
是 JS
的原生运算符,如果指定属性在指定对象中,则 in
运算符返回 true
,而对于 ts
来说,这种语法 [P in K]
可以用来遍历联合类型里的类型(注意,遍历每一个),这也就是为什么不用 [P extends K]
,作用不一样哈
T[P]
的意思就比较好理解了,interface
具象化为对象,T[P]
不就是表示 T
中属性为 P
的类型值吗?即 Todo['title']: string
至此 Pick
实现,关键点为 keyof
,extends
,in
,泛型