一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
前置知识
今日【middle - 9.deep_readonly】
在这之前需要掌握一点Ts基础知识,可以参考学习记录TypeScript学习记录-[数据类型]、TypeScript学习记录-[类和接口]、TypeScript学习记录-[枚举和泛型]、TypeScript学习记录-[类型别名、类型断言、声明文件]
二、题目分析
DeepReadonly
实现一个通用的DeepReadonly,它将对象的每个参数及其子对象递归地设为只读。
type DeepReadonly<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<DeepReadonly<X>, Expected>>,
]
type X = {
a: () => 22
b: string
c: {
d: boolean
e: {
g: {
h: {
i: true
j: 'string'
}
k: 'hello'
}
l: [
'hi',
{
m: ['hey']
},
]
}
}
}
type Expected = {
readonly a: () => 22
readonly b: string
readonly c: {
readonly d: boolean
readonly e: {
readonly g: {
readonly h: {
readonly i: true
readonly j: 'string'
}
readonly k: 'hello'
}
readonly l: readonly [
'hi',
{
readonly m: readonly ['hey']
},
]
}
}
}
- 我们知道JS的数据类型由七种原始数据类型( Number, Boolean, String, Symbol, Bigint, Null, Undefined)以及对象(Objects)和函数(functions)两个基本元素构成,此外还有一些特殊的诸如
Date、RegExp等对象元素。一个通用的DeepReadonly需要为所有原始数据类型添加readonly修饰符,而对于函数元素、RegExp等对象元素来说并不需要,因此必然会分情况讨论。面对嵌套的Object也必然会使用递归。 - 普通的
Readonly仅需一次循环即可为所有的属性添加readonly修饰 ,即type DeepReadonly<T, keys extends keyof T> = { readonly [key in keys]: T[key] },其中keys extends keyof T对联合类型keys进行类型约束,确保后续使用in遍历keys时的每一个类型key均在泛型T的类型中。但目前的DeepReadonly有两点不同,一是无法确定类型key是否是原始类型,不能直接的在前面添加readonly;二是无法确定类型key是否嵌套包含了其他类型,因此需要递归处理。 - 针对第一点,需要逐一对类型做枚举,即
type DeepReadonly<T, keys extends keyof T> = T extends Function ? T : readonly [key in keys]: T[key] }这里仅以Function元素为例,使用extends ? :进行条件判断,如果泛型T是Function直接返回就好,否则才进行下一步。 - 针对第二点,需要对类型
key进行递归调用,即type DeepReadonly<T, keys extends keyof T> = T extends Function ? T : readonly [key in keys]: DeepReadonly<T[key]> }。
一个完善的DeepReadonly可以参考vuejs/core