
本文深入探讨了在typescript中构建一个能够递归地提取类字段属性、排除函数、并正确处理可选性及各种嵌套数据结构(如对象、数组、map、set)的深层可写(deepwritable)类型。文章详细分析了导致“类型实例化深度过大”错误的原因,并提供了一种优化后的解决方案,确保类型安全和性能。
在TypeScript中,我们经常需要处理复杂的数据结构,特别是在需要修改对象部分属性时,保持类型安全至关重要。一个常见的需求是创建一个“深层可写”类型,它能够递归地遍历一个类的所有可写字段,同时排除方法(函数),并正确处理字段的可选性。然而,在实现此类递归类型时,我们可能会遇到TypeScript的“类型实例化深度过大且可能无限”(Type instantiation is excessively deep and possibly infinite)错误。本文将详细解析这个问题,并提供一个健壮的解决方案。
我们的目标是为类的 set 方法提供一个类型安全的参数,该参数允许我们更新类的部分属性,且这些属性必须是可写的,并能深入到嵌套的对象、数组、Map和Set中。
初始尝试通常会涉及以下几个关键类型:
然而,在实现 DeepWritable 时,尤其是当它需要处理 Map、Set、数组以及普通对象等多种递归结构时,很容易触及TypeScript的类型检查深度限制,导致上述错误。
在深入解决递归问题之前,我们先定义一些基础的辅助类型:
// 1. IfEquals: 比较两个类型是否完全相等
type IfEquals<X, Y, A = X, B = never> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? A : B;
// 2. WritableKeys: 提取类型T中可写的(非函数、非只读)属性键
type WritableKeys<T> = {
  [P in keyof T]: T[P] extends Function ? never : IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];
// 3. DeepWritablePrimitive: 定义深层可写类型中的基本类型
type DeepWritablePrimitive = undefined | null | boolean | string | number | Function;WritableKeys 通过 IfEquals 巧妙地判断属性是否为只读,并通过 T[P] extends Function ? never : ... 排除函数类型。
原始的 DeepWritable 类型在处理 Map 和 Set 等复杂泛型时,可能因为检查顺序或内部实现方式导致递归深度过大。为了解决这个问题,我们需要优化 DeepWritable 的结构,特别是对不同数据结构的判断顺序。
关键的改进点在于:
下面是优化后的 DeepWritable 及其辅助类型 DeepWritableRecord:
// 优化后的 DeepWritable 类型
type DeepWritable<T> =
  | T extends DeepWritablePrimitive ? T // 基本类型直接返回
  : T extends (infer U)[] ? DeepWritable<U>[] // 数组递归处理元素
  : T extends Map<any, any> ? ( // 优先处理 Map 类型
      T extends Map<infer K, infer V> ? Map<K, DeepWritable<V>> : never
    )
  : T extends Set<infer V> ? Set<DeepWritable<V>> // Set 递归处理元素
  : DeepWritableRecord<T>; // 其他对象类型递归处理
// 优化后的 DeepWritableRecord 类型,保留可选性
type DeepWritableRecord<T> = {
  // 使用 Pick 筛选出可写键,并保留其原始的可选性
  [K in keyof Pick<T, WritableKeys<T>>]: DeepWritable<T[K]>
}解释:
让我们将这些类型应用到一个实际的类结构中:
// 辅助类型定义 (如上所示)
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]: T[P] extends Function ? never : IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];
type DeepWritablePrimitive = undefined | null | boolean | string | number | Function;
// 优化后的 DeepWritable 类型定义 (如上所示)
type DeepWritable<T> =
  | T extends DeepWritablePrimitive ? T
  : T extends (infer U)[] ? DeepWritable<U>[]
  : T extends Map<any, any> ? (
      T extends Map<infer K, infer V> ? Map<K, DeepWritable<V>> : never
    )
  : T extends Set<infer V> ? Set<DeepWritable<V>>
  : DeepWritableRecord<T>;
type DeepWritableRecord<T> = {
  [K in keyof Pick<T, WritableKeys<T>>]: DeepWritable<T[K]>
}
// 示例类结构
class Base {
  // set 方法接受一个 Partial<DeepWritable<typeof this>> 类型的参数
  set(data?: Partial<DeepWritable<typeof this>>) {
    Object.assign(this, data);
  }
}
class Parent extends Base {
  name?: string; // 可选属性
  age: number = 0; // 必选属性
  readonly id: string = 'some-id'; // 只读属性,应被排除
  private _secret: string = 'secret'; // 私有属性,类型系统通常不直接处理,但如果通过公共方法暴露则需注意
  someMethod() { return 'hello'; } // 方法,应被排除
  arr?: Parent[]; // 嵌套数组
  dataMap?: Map<string, Parent>; // 嵌套 Map
  dataList?: Set<string>; // 嵌套 Set
}
const record = new Parent();
// 使用 set 方法更新属性
record.set({
  name: 'New Name', // 更新可选属性
  age: 30, // 更新必选属性
  // id: 'new-id', // 错误:'id' 是只读属性,已被 WritableKeys 排除
  // someMethod: () => 'new hello', // 错误:'someMethod' 是函数,已被 WritableKeys 排除
  arr: [{ // 嵌套数组
    name: 'Child 1',
    age: 5
  }, {
    name: 'Child 2',
    arr: [{ name: 'Grandchild 1' }] // 更深层次的嵌套
  }],
  dataMap: new Map([ // 嵌套 Map
    ['key1', { name: 'Map Child 1', age: 10 }],
    ['key2', { name: 'Map Child 2' }]
  ]),
  dataList: new Set(['item1', 'item2']) // 嵌套 Set
});
console.log(record);
console.log(record.arr?.[0]?.name); // Child 1
console.log(record.arr?.[1]?.arr?.[0]?.name); // Grandchild 1
console.log(record.dataMap?.get('key1')?.name); // Map Child 1
// 验证类型检查:尝试设置只读属性或方法会报错
// record.set({ id: 'test' }); // Error: Type '{ id: string; }' has no properties in common with type 'Partial<DeepWritable<Parent>>'.
// record.set({ someMethod: () => {} }); // Error: Type '{ someMethod: () => void; }' has no properties in common with type 'Partial<DeepWritable<Parent>>'.通过上述优化,我们成功构建了一个健壮的 DeepWritable 类型,它不仅能够递归地处理复杂的嵌套结构,排除不必要的属性,还能有效避免TypeScript的类型实例化深度限制,为应用程序提供了更强大的类型安全保障。
以上就是深入理解TypeScript递归类型:构建深层可写属性并规避深度限制的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                 
                                
                                 收藏
收藏
                                                                             
                                
                                 收藏
收藏
                                                                             
                                
                                 收藏
收藏
                                                                            Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号