递归排除Typescript中的只读属性

Recursively exclude readonly properties in Typescript

我很确定我缺乏使用复杂泛型的经验,所以我希望有人对如何实现这一点有想法。我的用例是为我的 React/Formik 表单值创建 "form types",而无需重新键入新定义或传入具有大量不可更新属性的完整对象。

我找到了这个答案,它展示了如何从 TypeScript 类型中排除只读属性,但我发现很难全神贯注地让它递归。此外,我希望它省略 return 空嵌套对象的属性(如果可能)。

type IfEquals<X, Y, A=X, B=never> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? A : B;

type WritableKeys<T> = {
  [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];

type ReadonlyKeys<T> = {
  [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
}[keyof T];

type Writable<T> = Pick<T, WritableKeys<T>>

type Brand = {
  readonly id: number;
  name: string;
  logo: {
    readonly id: number;
    url: string;
  },
  creator: {
    readonly id: number;
  }
};

type EditableBrand = Writable<Brand>;
// type EditableBrand = {
//   name: string;
//   logo: {
//     url: string;
//   }
// };

有一个suggestion for a DeepReadonly<T> type in TypeScript repo and some suggested implementations as well. And the same question on Whosebug. Here。是如何实现 DeepReadonly 的示例。您可以使用相同的技术来实现 DeepWritable type:

type IfEquals<X, Y, A=X, B=never> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? A : B;

type WritableKeys<T> = {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];

type DeepWritablePrimitive = undefined | null | boolean | string | number | Function;
type DeepWritable<T> =
    T extends DeepWritablePrimitive ? T :
    T extends Array<infer U> ? DeepWritableArray<U> :
    T extends Map<infer K, infer V> ? DeepWritableMap<K, V> :
    T extends Set<infer T> ? DeepWriableSet<T> : DeepWritableObject<T>;

type DeepWritableArray<T> = Array<DeepWritable<T>>;
type DeepWritableMap<K, V> = Map<K, DeepWritable<V>>;
type DeepWriableSet<T> = Set<DeepWritable<T>>;

type DeepWritableObject<T> = {
    [K in WritableKeys<T>]: DeepWritable<T[K]>
};

Playground


然后让我们扩展类型以省略 return 清空嵌套对象的键:

...

type EmptyKeys<T> = {
  [P in keyof T]: {} extends T[P] ? P : never
}[keyof T];

type OmitEmptyKeys<T> = Omit<T, EmptyKeys<T>>;
type DeepWritable<T> = ... : OmitEmptyKeys<DeepWritableObject<T>>;

Playground