type-challenges:Deep Readonly

43 阅读2分钟

Deep Readonly

问题描述

实现一个泛型 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>>,
  Expect<Equal<DeepReadonly<X3>, Expected3>>,
  Expect<Equal<DeepReadonly<X4>, Expected4>>
]
​
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 X3 = { a: () => 22 }
type X4 = { a: Map<string, 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 }
type Expected3 = { readonly a: () => 22 }
type Expected4 = { readonly a: ReadonlyMap<string, number> }
​
// ============= Your Code Here =============
// 答案1
type DeepReadonly<T extends {}> = {
  readonly [K in keyof T]: T[K] extends {}
    ? T[K] extends () => {}
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K]
}
​
// 答案2
type DeepReadonly<T> = {
  readonly [key in keyof T]: keyof T[key] extends never
    ? T[key]
    : DeepReadonly<T[key]>
}
​
// 答案3
type DeepReadonly<T> = T extends Function
  ? T
  : T extends object | unknown[]
  ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
  : T
​
// 答案4
type Primitive = string | number | boolean | bigint | symbol | undefined | null
type Builtin = Primitive | Function | Date | Error | RegExp
type DeepReadonly<T> = T extends Builtin
  ? T
  : T extends Map<infer K, infer V>
  ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends ReadonlyMap<infer K, infer V>
  ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends WeakMap<infer K, infer V>
  ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends Set<infer U>
  ? ReadonlySet<DeepReadonly<U>>
  : T extends ReadonlySet<infer U>
  ? ReadonlySet<DeepReadonly<U>>
  : T extends WeakSet<infer U>
  ? WeakSet<DeepReadonly<U>>
  : T extends Promise<infer U>
  ? Promise<DeepReadonly<U>>
  : T extends {}
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : Readonly<T>
​
​

这题的思路首先是判断当前是否是对象的最后一层,不是则继续递归,一直判断到最后一层为止。

这里的思路很简单,首先将所有的键名变为只读属性 readonly [K in keyof T],其次判断键值是否继承自 {} 但是这里会判断不准确,()=>22 的也会判断为 true,所以需要继续判断一层 T[K] extends () => {},然而,答案1,2,3 只能符合某些类型,范围约束不明确,并不能将所有的类型都判断完整,这里的测试用例4就不能通过,所以最完整的判断应该是答案4,这里的答案将所有基本类型和复杂类型都枚举了出来进行判断。