TS 类型体操笔记 - 57 Get Required, 59 Get Optional, 89 Required Keys, 90 Optional Keys

412 阅读4分钟

概述

类型体操 是关于 TS 类型编程的一系列挑战(编程题目)。通过这些挑战,我们可以加深对 TS 类型系统的理解,举一反三,在实际工作中解决类似问题。

本文是我对以下题目的解题笔记:

这次为什么要打包写笔记呢?因为这几个题本质上是一回事,任意解开一个,其他的就全解开了,所以一起说。

本文的读者是正在做 TS 类型体操,对上述题目感兴趣,希望获得更多解读的同学。读者需要具备一定的 TS 基础知识,这部分推荐阅读另一篇优秀博文 Typescript 类型编程,从入门到念头通达😇

题目和答案

我们从 57 Get Required 入手,先来概览一下题目和答案。

题目

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 }

答案

Issue 20148

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

同理,只需要构造一个 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

利用题目 57 中的 GetRequired 类型,这题的答案就是

type RequiredKeys<T> = keyof GetRequired<T>

解 90 Optional Keys

利用题目 59 中的 GetOptional 类型,这题的答案就是

type OptionalKeys<T> = keyof GetOptional<T>

相关知识点

+?, -? 是啥意思?在 Mapped Types 中,readonly, ? 这些关键字都叫做字段的『修饰符』,通过 +、- 符号可以强制给字段增加或删除这些修饰符。

  • +? 强制让字段变成 optional
  • -? 强制让字段变成 required

意外收获