题目描述
实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
例如
type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
type Todo = DeepReadonly<X> // should be same as `Expected`
题解
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'
type cases = [
Expect<Equal<DeepReadonly<X1>, Expected1>>,
Expect<Equal<DeepReadonly<X2>, Expected2>>,
]
type X1 = {
a: () => 22
b: string
c: {
d: boolean
e: {
g: {
h: {
i: true
j: 'string'
}
k: 'hello'
}
l: [
'hi',
{
m: ['hey']
},
]
}
}
}
type X2 = { a: string } | { b: number }
type Expected1 = {
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']
},
]
}
}
}
type Expected2 = { readonly a: string } | { readonly b: number }
// ============= Your Code Here =============
type DeepReadonly<T> = {
readonly [P in keyof T]:
keyof T[P] extends never
? T[P]
: DeepReadonly<T[P]>
}
readonly [P in keyof T]用于遍历对象T的键,并将键存储在P中
keyof T[P] extends never用于检查T[P]是否没有任何键:
- 对于一些基本数据类型(如
null、undefined)和函数类型,keyof的结果是never - 对于对象类型,如果没有可枚举属性,
keyof的结果也是never - 对于
string、number、boolean,keyof的结果不为never,因为 TypeScript 将它们视为对应的包装对象类型,尽管这些基本数据类型本身没有可枚举属性
type BooleanKeys = keyof boolean; // "valueOf"
type NumberKeys = keyof number; // "toString" | "valueOf" | "toFixed" | "toExponential" | "toPrecision" | "toLocaleString"
type StringKeys = keyof string; // number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | ... 30 more ... | "padEnd"
当keyof T[P]不为never时,会递归调用DeepReadonly<T[P]>以将其内部属性也设为只读
对于string、number、boolean,虽然keyof的结果不为never,会进入DeepReadonly<T[P]>分支,但这些类型实际上没有自己的可枚举属性,所以映射类型会自动“跳过”它们,直接返回输入的基本数据类型
type MappedType<T> = {
[K in keyof T]: T;
}
type T0 = MappedType<string>; // string