概述
类型体操 是关于 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”
答案
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
a 和 b 的值都是 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 字段。
有两种思路
- 先基于 mapped type 和 key remapping 构造一个仅含有 readonly keys 的对象,然后提取其所有 keys
- 先提取对象所有的 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>>
完了。