概述
类型体操 是关于 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>>
完了。