[TypeScript] Type Challenges #9 - Deep Readonly

61 阅读2分钟

题目描述

实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。

您可以假设在此挑战中我们仅处理对象。不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。

例如

type X = { 
  x: { 
    a1
    b'hi'
  }
  y'hey'
}

type Expected = { 
  readonly x: { 
    readonly a1
    readonly b'hi'
  }
  readonly y'hey' 
}

type Todo = DeepReadonly<X> // should be same as `Expected`

题解

// ============= Test Cases =============
import type { EqualExpect } from './test-utils'

type cases = [
  Expect<Equal<DeepReadonly<X1>, Expected1>>,
  Expect<Equal<DeepReadonly<X2>, Expected2>>,
]

type X1 = {
  a() => 22
  bstring
  c: {
    dboolean
    e: {
      g: {
        h: {
          itrue
          j'string'
        }
        k'hello'
      }
      l: [
        'hi',
        {
          m: ['hey']
        },
      ]
    }
  }
}

type X2 = { astring } | { bnumber }

type Expected1 = {
  readonly a() => 22
  readonly bstring
  readonly c: {
    readonly dboolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly itrue
          readonly j'string'
        }
        readonly k'hello'
      }
      readonly lreadonly [
        'hi',
        {
          readonly mreadonly ['hey']
        },
      ]
    }
  }
}

type Expected2 = { readonly astring } | { readonly bnumber }


// ============= 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]是否没有任何键:

  • 对于一些基本数据类型(如nullundefined)和函数类型,keyof的结果是never
  • 对于对象类型,如果没有可枚举属性,keyof的结果也是never
  • 对于stringnumberbooleankeyof的结果不为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]>以将其内部属性也设为只读

对于stringnumberboolean,虽然keyof的结果不为never,会进入DeepReadonly<T[P]>分支,但这些类型实际上没有自己的可枚举属性,所以映射类型会自动“跳过”它们,直接返回输入的基本数据类型

type MappedType<T> = {
    [K in keyof T]: T;
}

type T0 = MappedType<string>; // string