Yuzhe's Blog

yuzhes

对象属性只读(递归)

对象属性只读(递归)

题目链接

题目

实现一个泛型 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`

解答

:::tip

本题通过测试样例并不代表解答完全正确。本题将提供一个基本的解答用于启发思路,和一个在Vue.js源代码中的实现以供参考。

:::

type DeepReadonly<T> = {
  readonly [k in keyof T]: 
  T[k] extends Record<any, any> // 如果是对象
  ? T[k] extends Function // 如果是函数(注意,这里没考虑WeakMap等类型)
    ? T[k] // 不处理
    : DeepReadonly<T[k]> // 递归地设为只读
  : T[k] // 视为基本类型
}

Vue.js源代码中的实现:

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>

注意到,对于某些内置的类型,设置为只读后,并不返回原类型。当这些数据结构被设置为只读后,应移除产生修改的方法,例如setdelete等。