概述
类型体操 是关于 TS 类型编程的一系列挑战(编程题目)。通过这些挑战,我们可以加深对 TS 类型系统的理解,举一反三,在实际工作中解决类似问题。
本文是我对以下题目的解题笔记:
这次为什么要打包写笔记呢?因为这几个题本质上是一回事,任意解开一个,其他的就全解开了,所以一起说。
本文的读者是正在做 TS 类型体操,对上述题目感兴趣,希望获得更多解读的同学。读者需要具备一定的 TS 基础知识,这部分推荐阅读另一篇优秀博文 Typescript 类型编程,从入门到念头通达😇。
题目和答案
我们从 57 Get Required 入手,先来概览一下题目和答案。
题目
Implement the advanced util type GetRequired<T>
, which remains all the required fields
For example
type I = GetRequired<{ foo: number, bar?: string }> // expected to be { foo: number }
答案
type IsRequiredKey<T, K extends keyof T> =
{[P in K]: T[P]} extends {[P in K]-?: T[P]}
? true
: false
type GetRequired<T> = {
[P in keyof T as IsRequiredKey<T, P> extends true ? P : never]: T[P]
}
解题思路
这个题的关键点是:
- 一个 optional 字段要比一个 required 字段更加宽泛
- 一个 required 字段要比一个 optional 字段更加具体
- 如果两个对象的唯一区别是,有一个字段,它在一个对象中是 required,在另一个对象中是 optional,那么带有 required 字段的对象是带有 optional 对象的子类型
{a: 1} extends {a?: 1}
为真,但{a?: 1} extends {a: 1}
为假
上面四句话是从不同角度对同一条规则的等价描述,读者能理解就好。
理解了这个关键点,答案中的 IsRequiredKey
类型理解起来就比较容易了。IsRequiredKey
用于判断一个对象 T 中的指定字段 K 是不是 required 的。它在 extends
左侧构造了一个只含有 a
字段的对象,在右侧构造了一个只含有 a
字段且强制 required 的对象。它主要利用的逻辑是:
{a: 1} extends {a: 1}
is true{a?: 1} extends {a: 1}
is false
代入看看,如果原本的 a
字段是 required 的,那么将返回 true;如果原本的 a
字段是 optional 的,那么将返回 false,是吧 :)
有了 IsRequiredKey
这个工具之后,GetRequired
实现起来就很容易了,主要就利用 Key Remapping 来过滤一下字段就可以了。
举一反三
开篇说过,只需要解一个题,其他三个题就都解了。
解 59 Get Optional
- 题目:59 Get Optional
- 答案:Issue 20149
同理,只需要构造一个 IsOptionalKey
工具类型就可以了。
一个简单的方法,粗略的说,optional = !required
,所以我们只需要将上面的 IsRequiredKey
中返回的 true 和 false 对换一下位置就可以了:
type IsRequiredKey<T, K extends keyof T> =
{[P in K]: T[P]} extends {[P in K]-?: T[P]}
? true
: false
// 聪明如我
type IsOptionalKey<T, K extends keyof T> =
{[P in K]: T[P]} extends {[P in K]-?: T[P]}
? false
: true
还有其他方法。上述方案中,我们是将 extends
右侧固定为了 required 字段,控制变化发生在左侧。我们还可以将 extends
的左侧固定为 optional 字段,控制变化发生在右侧,例如:
type IsOptionalKey<T, K extends keyof T> =
{[P in K]+?: T[P]} extends {[P in K]: T[P]}
? true
: false
至于逻辑的正确性,可以对照上面『解题思路』中提到的关键点,代入具体的 case 进行验证,这里不再重复。
解 89 Required Keys
- 题目:89 Required Keys
- 答案:Issue 20151
利用题目 57 中的 GetRequired
类型,这题的答案就是
type RequiredKeys<T> = keyof GetRequired<T>
解 90 Optional Keys
- 题目:90 Optional Keys
- 答案:Issue 20152
利用题目 59 中的 GetOptional
类型,这题的答案就是
type OptionalKeys<T> = keyof GetOptional<T>
相关知识点
+?
, -?
是啥意思?在 Mapped Types 中,readonly
, ?
这些关键字都叫做字段的『修饰符』,通过 +、- 符号可以强制给字段增加或删除这些修饰符。
+?
强制让字段变成 optional-?
强制让字段变成 required
意外收获
- 2857 IsRequiredKey 一并解决了