TS 类型体操笔记 - 5181 Mutable Keys

117 阅读3分钟

概述

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

本文是我对 5181 Mutable Keys 的解题笔记。

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

题目和答案

先直接贴出题目与答案,呈现问题全貌。

题目

Implement the advanced util type MutableKeys, which picks all the mutable (not readonly) keys into a union.

For example:

type Keys = MutableKeys<{ readonly foo: string; bar: number }>;
// expected to be “bar”

答案

Issue 21954

type IsEqual<A, B> = 
  (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
  ? true 
  : false

type IsReadonlyKey<O, K extends keyof O, _OPK = Pick<O, K>> = IsEqual<_OPK, Readonly<_OPK>>

type ReadonlyKeys<T, K extends keyof T = keyof T> = 
  K extends unknown
  ? IsReadonlyKey<T, K> extends true ? K : never
  : never

type MutableKeys<T> = Exclude<keyof T, ReadonlyKeys<T>>

解题思路

IsEqual 问题

由于之前做过类似的 Required Keys 和 Optional Keys(参考 这篇文章),我首先想到的是用类似的方法做一个 IsReadonlyKey,但验证之后发现,readonly 的字段和 non-readonly 的字段没有办法单纯的通过 extends 进行区别,例如这段代码片段:

type a = {a: string} extends {readonly a: string} ? 1 : 2
type b = {readonly a: string} extends {a: string} ? 1 : 2

ab 的值都是 1。因此,我们需要一个更加严格的 Equal 工具类型,而这个 Equal 类型恰恰是本题的解题关键。

恰恰,type challengs 提供的用于检查结果的 Equal 就满足上述诉求。参考 源码,我们实现一个 IsEqual 类型:

type IsEqual<A, B> = 
  (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
  ? true 
  : false

它大概利用了条件类型的赋值规则,并依赖了 ts 的内部实现。我暂时不能准确理解,所以也不在这里强行解释了。

IsReadonlyKey 问题

总之,解决了 Equal 问题之后, IsReadonlyKey 就很容易实现了:

type IsReadonlyKey<O, K extends keyof O, _OPK = Pick<O, K>> = IsEqual<_OPK, Readonly<_OPK>>

IsReadonlyKey 接收一个对象 O,并指定一个它的字段 key K,如果这个字段是 readonly,则返回 true,否则返回 false

ReadonlyKeys 问题

有了 IsReadonlyKey 之后,就可以继续实现 ReadonlyKeys。它接收一个对象 T,返回另一个对象,仅包含 T 中的 readonly 字段。

有两种思路

  1. 先基于 mapped typekey remapping 构造一个仅含有 readonly keys 的对象,然后提取其所有 keys
  2. 先提取对象所有的 keys,然后利用 distributive conditional type,筛选出 readonly keys

在之前的 Required Keys 和 Optional Keys 挑战中,我们采用的是上面第一种思路,所以这里就用第二种思路来实现:

type ReadonlyKeys<T, K extends keyof T = keyof T> = 
  K extends unknown
  ? IsReadonlyKey<T, K> extends true ? K : never
  : never

MutableKeys 问题

有了 ReadonlyKeys 之后,MutableKeys 就很容易实现了。从逻辑上说,就是从对象所有的 keys 中排除掉 readonly keys 即可:

type MutableKeys<T> = Exclude<keyof T, ReadonlyKeys<T>>

完了。

参考