开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
之前深信服前端一面的时候,面试官问我会 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,泛型