TypeScript深度解析:递归获取类字段属性,解决类型深度实例化问题

TypeScript深度解析:递归获取类字段属性,解决类型深度实例化问题

本文深入探讨了在typescript中如何安全地递归提取类的可写字段属性,同时排除函数类型并保留其可选性。通过优化`deepwritable`类型定义,特别是针对`map`类型的处理顺序以及使用`pick`来精确控制属性,成功解决了`type instantiation is excessively deep and possibly infinite`这一常见递归类型错误,提供了健壮的类型解决方案。

引言:类属性的递归类型提取挑战

在TypeScript开发中,我们经常需要处理类实例的数据。一个常见的需求是,从一个包含复杂结构(如嵌套对象、数组、Map等)的类中,提取出其所有可写的非函数属性,以便进行数据更新或序列化。更进一步,我们希望这种提取是递归的,能够深入到嵌套对象内部,并且在过程中能够正确地保留原始属性的可选性。

然而,在尝试实现此类递归类型时,开发者常常会遇到一个令人困扰的错误:Type instantiation is excessively deep and possibly infinite.(类型实例化过深,可能无限循环)。这通常发生在TypeScript编译器在尝试解析一个复杂或循环的类型定义时,达到了其内部的递归深度限制。

本文将详细分析导致此问题的原因,并提供一个经过优化的解决方案,确保类型安全、属性可选性,并有效避免深度实例化错误。

问题根源分析:初始尝试的局限性

为了实现上述目标,我们通常会构建一系列辅助类型。以下是一个常见的初始尝试思路及其可能导致的问题:

识别可写属性并排除函数:我们使用条件类型 IfEquals 和映射类型 WritableKeys 来过滤掉只读属性和函数类型的属性。

type IfEquals =  (() => T extends X ? 1 : 2) extends  (() => T extends Y ? 1 : 2) ? A : B;type WritableKeys = {  [P in keyof T]: T[P] extends Function ? never : IfEquals}[keyof T];

IfEquals 用于判断两个类型是否完全相等,这里用来区分只读和可写属性。WritableKeys 则利用此判断,结合 T[P] extends Function ? never : … 来排除函数,最终返回所有可写非函数属性的键名。

递归深度遍历类型:DeepWritable为了处理嵌套结构,我们需要一个递归类型 DeepWritable。

type DeepWritablePrimitive = undefined | null | boolean | string | number | Function;type DeepWritable =    T extends DeepWritablePrimitive ? T :    T extends Array ? DeepWritableArray :    T extends Map ? DeepWritableMap : // 问题点1:Map的处理顺序    T extends Set ? DeepWriableSet : DeepWritableObject;type DeepWritableArray = Array<DeepWritable>;type DeepWritableMap = Map<K, DeepWritable>;type DeepWriableSet = Set<DeepWritable>;type DeepWritableObject = {    [K in WritableKeys]: DeepWritable // 问题点2:丢失可选性};

这个 DeepWritable 类型试图递归地将所有可写属性转换为其深层可写版本。然而,它存在两个主要问题:

Map 类型的处理顺序导致深度实例化错误: 当一个复杂类型在递归过程中可能间接或直接地被推断为 Map 时,T extends Map 这种精确的 infer 模式可能会导致编译器进行过度的类型推断,从而触发深度实例化限制。尤其当 T 本身是一个复杂的联合类型或泛型时,这种推断路径会变得非常深。丢失属性的可选性: 在 DeepWritableObject 中,[K in WritableKeys]: DeepWritable 这种映射类型会默认将所有映射的属性变为必需的。例如,如果原始类中有一个属性 name?: string,经过 WritableKeys 过滤后 name 键依然存在,但在 DeepWritableObject 中它会变成 name: string,从而丢失了可选性(?)。

解决方案:优化递归类型定义

为了解决上述问题,我们需要对 DeepWritable 类型进行关键的优化。

1. 核心改进1:Map 类型的精确处理

为了避免在 Map 类型推断时触发深度实例化错误,我们应该首先进行一个更宽松的 Map 类型检查,然后再进行带有 infer 的精确推断。这样可以为编译器提供一个更直接的路径,避免不必要的深度递归。

将 T extends Map 的检查改为两步:

百度虚拟主播 百度虚拟主播

百度智能云平台的一站式、灵活化的虚拟主播直播解决方案

百度虚拟主播 36 查看详情 百度虚拟主播 首先,检查 T extends Map。如果匹配,说明 T 确实是一个 Map。然后,在这个确定的 Map 分支内部,再使用 T extends Map 来安全地提取键值类型并递归处理。

这种处理顺序能够有效“短路”掉不必要的复杂类型推断,从而避免深度实例化问题。

2. 核心改进2:保留属性的可选性

要保留属性的可选性,我们不能直接映射 WritableKeys。正确的做法是,首先使用 Pick<T, WritableKeys> 创建一个新类型,这个新类型只包含 T 中可写的键,并且最重要的是,它会保留这些键在 T 中的可选性。然后,我们再对这个新类型的键进行映射。

type DeepWritableRecord1 = {  // 使用 Pick<T, WritableKeys> 来保留可选性  [K in keyof Pick<T, WritableKeys>]: DeepWritable1}

通过 keyof Pick<T, WritableKeys>,我们得到的键集合不仅是可写的,而且当这些键被映射时,它们将继承 Pick 结果中的可选性。

完整的优化类型定义

结合上述改进,以下是经过优化的类型定义:

// 用于判断类型是否完全相等,以区分只读和可写属性type IfEquals =  (() => T extends X ? 1 : 2) extends  (() => T extends Y ? 1 : 2) ? A : B;// 提取可写(非只读)且非函数属性的键名type WritableKeys = {  [P in keyof T]: T[P] extends Function ? never : IfEquals}[keyof T];// 基础的可写原始类型,不需进一步递归type DeepWritablePrimitive = undefined | null | boolean | string | number | Function;// 递归地将类型转换为深层可写版本,并处理特殊类型type DeepWritable1 =  | T extends DeepWritablePrimitive ? T // 基础类型直接返回  : T extends (infer U)[] ? DeepWritable1[] // 数组类型递归处理其元素  : T extends Map ? ( // 关键优化:先检查是否为Map,避免深度实例化错误    T extends Map ? Map<K, DeepWritable1> : never // 然后提取K, V并递归处理V  )  : T extends Set ? Set<DeepWritable1> // Set类型递归处理其元素  : DeepWritableRecord1; // 其他对象类型交由 DeepWritableRecord1 处理// 专门处理对象类型,确保保留属性的可选性type DeepWritableRecord1 = {  // 使用 Pick<T, WritableKeys> 来筛选可写键并保留其可选性  [K in keyof Pick<T, WritableKeys>]: DeepWritable1}

实际应用:在类方法中集成

有了这些优化后的类型定义,我们现在可以在类的 set 方法中安全地使用它们,以确保传入的数据符合预期的结构和类型要求。

class Base {  // set 方法接受一个 Partial<DeepWritable1> 类型的参数  // 确保传入的数据是当前类实例的可写、非函数属性的深层可写版本,且属性可选  set(data?: Partial<DeepWritable1>) {    // 使用 Object.assign 安全地合并数据    Object.assign(this, data);  }}class Parent extends Base {  name?: string; // 可选属性  age: number = 0; // 必需属性  readonly id: string = 'abc'; // 只读属性,应被排除  // 嵌套数组  arr?: Parent[];  // 嵌套Map  config?: Map;  // 函数属性,应被排除  greet() { console.log('Hello'); }};const record = new Parent();record.set({  name: 'Alice', // name 是可选的,可以设置  age: 30,       // age 是必需的,可以设置  // id: 'xyz',  // 错误:id 是只读属性,不能设置  arr: [{    name: 'Child 0',    age: 5  }, {    name: 'Child 1',    // age: 'ten' // 错误:age 必须是 number 类型  }],  config: new Map([['theme', 'dark']]) // Map 类型正确处理  // greet: () => {} // 错误:greet 是函数,不能设置});console.log(record.name); // Aliceconsole.log(record.age);  // 30console.log(record.arr);  // [{ name: 'Child 0', age: 5 }, { name: 'Child 1' }]console.log(record.config?.get('theme')); // dark// 尝试设置不存在的属性或错误类型会报错// record.set({ unknownProp: 'value' }); // 错误:对象文字可以只指定已知属性

在上述示例中,record.set() 方法现在能够严格地约束传入的数据类型,只允许设置 Parent 类中可写且非函数的属性,同时保留了属性的可选性,并且支持嵌套结构如数组和 Map 的深层递归处理。最重要的是,它避免了 Type instantiation is excessively deep 错误。

注意事项与总结

条件类型顺序的重要性: 在使用联合类型进行条件类型推断时,类型检查的顺序至关重要。对于像 Map 这样的复杂类型,先进行一个更通用的检查 (T extends Map),再进行带有 infer 的精确推断,可以显著提高编译器性能并避免深度实例化错误。保留可选性的技巧: Pick 类型在保留原始类型 T 中指定键 K 的可选性方面非常有用。结合 keyof 和映射类型,可以精确控制最终类型的结构。理解深度实例化错误: Type instantiation is excessively deep and possibly infinite 错误通常是递归类型定义中存在无限循环、过于复杂的推断路径或编译器无法有效短路的类型分支所导致。通过简化推断路径和优化检查顺序,可以有效解决此类问题。类型安全与运行时: TypeScript 类型检查只在编译时生效,运行时仍然是 JavaScript。Object.assign 等操作在运行时不会进行类型验证,因此确保编译时的类型安全至关重要。

通过本文介绍的优化方法,开发者可以更自信地构建复杂的递归类型,从而在TypeScript项目中实现更高级别的类型安全和代码健壮性。

以上就是TypeScript深度解析:递归获取类字段属性,解决类型深度实例化问题的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/309869.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
win11此电脑的图标不见了怎么办_win11此电脑图标消失修复方法
上一篇 2026年10月28日 14:09:05
html编辑器如何列模式编辑 html编辑器处理csv数据的利器
下一篇 2025年12月23日 00:36:09

相关推荐

  • 深入理解TypeScript递归类型:构建深层可写属性并规避深度限制

    深入理解TypeScript递归类型:构建深层可写属性并规避深度限制深入理解TypeScript递归类型:构建深层可写属性并规避深度限制深入理解TypeScript递归类型:构建深层可写属性并规避深度限制深入理解TypeScript递归类型:构建深层可写属性并规避深度限制

    本文深入探讨了在typescript中构建一个能够递归地提取类字段属性、排除函数、并正确处理可选性及各种嵌套数据结构(如对象、数组、map、set)的深层可写(deepwritable)类型。文章详细分析了导致“类型实例化深度过大”错误的原因,并提供了一种优化后的解决方案,确保类型安全和性能。 Ty…

    2026年10月28日 • 用户投稿
    000
  • Java自定义类toString()方法:优化对象打印输出

    Java自定义类toString()方法:优化对象打印输出Java自定义类toString()方法:优化对象打印输出Java自定义类toString()方法:优化对象打印输出Java自定义类toString()方法:优化对象打印输出

    本文旨在解决Java开发中,当使用`Arrays.toString()`打印包含自定义对象的数组时,默认输出对象哈希码而非其实际内容的问题。我们将详细讲解该现象发生的原因,并提供通过重写自定义类的`toString()`方法来优化对象打印输出,使其显示有意义的属性信息,从而提升代码可读性和调试效率的…

    2026年10月28日 • 用户投稿
    100
  • Safari浏览器闪退怎么办 Safari浏览器崩溃重启问题修复方法

    Safari浏览器闪退怎么办 Safari浏览器崩溃重启问题修复方法Safari浏览器闪退怎么办 Safari浏览器崩溃重启问题修复方法Safari浏览器闪退怎么办 Safari浏览器崩溃重启问题修复方法Safari浏览器闪退怎么办 Safari浏览器崩溃重启问题修复方法

    先清除缓存和网站数据,再关闭扩展、重启设备并更新系统,最后重置设置或修复系统。多数Safari闪退由缓存冲突或设置问题引起,按顺序操作可解决。 遇到Safari浏览器频繁闪退或崩溃重启,多数情况是缓存冲突、系统小故障或设置问题导致的。直接按顺序尝试以下方法,通常能快速解决。 清除浏览数据与缓存 积累…

    2026年10月28日 • 用户投稿
    000
  • JavaScript编译器优化技术与JIT原理

    JavaScript编译器优化技术与JIT原理JavaScript编译器优化技术与JIT原理JavaScript编译器优化技术与JIT原理JavaScript编译器优化技术与JIT原理

    JavaScript通过V8等引擎的多层JIT机制实现高效执行:源代码经解析生成AST,Ignition解释器转为字节码,热点函数由TurboFan基于类型推测等优化编译为机器码。引擎采用内联缓存、函数内联、逃逸分析等技术提升性能,但频繁改变变量类型或对象结构会触发去优化。开发者应保持类型稳定、避免…

    2026年10月28日 • 用户投稿
    000
  • SQL查询字符串构建:处理引号标识符的最佳实践

    本文探讨了在Java等语言中使用StringBuilder构建SQL查询时,如何正确处理SQL语句中包含双引号的标识符,例如数字作为列别名。核心解决方案包括通过反斜杠转义双引号字符,或采用更推荐的非引用标识符命名方式,以提升代码的可读性和维护性,并避免常见的语法错误。 在构建动态sql查询时,尤其是…

    2026年10月28日
    000
  • 如何使用正则表达式验证包含斜杠的URL Slug

    如何使用正则表达式验证包含斜杠的URL Slug如何使用正则表达式验证包含斜杠的URL Slug如何使用正则表达式验证包含斜杠的URL Slug如何使用正则表达式验证包含斜杠的URL Slug

    本文旨在提供一个实用的教程,指导开发者如何扩展现有的正则表达式,使其能够正确验证包含正斜杠(`/`)的URL slug。通过修改分隔符表达式,我们能够允许像 `my-parent-page/my-child-page` 这样的分层结构slug,同时保持其原有的字母数字和连字符验证规则,确保URL的有…

    2026年10月28日 • 用户投稿
    000
  • Java泛型嵌套类中类型参数的遮蔽与解析

    Java泛型嵌套类中类型参数的遮蔽与解析Java泛型嵌套类中类型参数的遮蔽与解析Java泛型嵌套类中类型参数的遮蔽与解析Java泛型嵌套类中类型参数的遮蔽与解析

    在java泛型编程中,当外部类和其嵌套的内部类都定义了同名的类型参数时,内部类的类型参数会遮蔽外部类的类型参数,导致外部类型参数在内部类中不可直接访问。本文将深入探讨这一现象,并提供通过使用不同类型参数名称来明确区分和访问外部及内部类型参数的解决方案,以提升代码的清晰度和可维护性。 理解泛型类型参数…

    2026年10月28日 • 用户投稿
    100
  • 扩展正则表达式以支持带斜杠的URL Slug验证

    扩展正则表达式以支持带斜杠的URL Slug验证扩展正则表达式以支持带斜杠的URL Slug验证扩展正则表达式以支持带斜杠的URL Slug验证扩展正则表达式以支持带斜杠的URL Slug验证

    本文旨在探讨如何改进正则表达式,使其能够有效验证包含斜杠(`/`)的url slug。通过分析现有正则表达式的局限性,我们将介绍一种修改方案,允许在slug中使用斜杠作为分隔符,从而支持如“父页面/子页面”等多层级结构的路径。文章将提供详细的正则表达式解析和代码示例,帮助开发者实现更灵活的slug验…

    2026年10月28日 • 用户投稿
    000
  • 将 Firebase 数据转换为 Dart Pojo 类的正确方法

    将 Firebase 数据转换为 Dart Pojo 类的正确方法将 Firebase 数据转换为 Dart Pojo 类的正确方法将 Firebase 数据转换为 Dart Pojo 类的正确方法将 Firebase 数据转换为 Dart Pojo 类的正确方法

    本文介绍了如何将从 Firebase 读取的数据转换为 Dart 中的 Pojo (Plain Old Java Object) 类,解决 `CartItem.fromJson` 方法在转换 Firebase 数据时出现空值问题的方案。通过示例代码展示了 `fromJson` 工厂方法的正确写法,并…

    2026年10月28日 • 用户投稿
    000
  • sublime怎么对齐代码中的等号或冒号_sublime代码对齐格式化技巧

    sublime怎么对齐代码中的等号或冒号_sublime代码对齐格式化技巧sublime怎么对齐代码中的等号或冒号_sublime代码对齐格式化技巧sublime怎么对齐代码中的等号或冒号_sublime代码对齐格式化技巧sublime怎么对齐代码中的等号或冒号_sublime代码对齐格式化技巧

    使用 Alignment 插件可高效对齐代码中的等号或冒号,提升可读性。首先通过命令面板安装插件,选中代码后运行 Align by Assignment 命令实现垂直对齐,支持多种编程语言。无插件时可用正则替换:开启查找替换面板,输入 (w+)s:s(.+) 替换为 $1{:>20}: $2 …

    2026年10月28日 • 用户投稿
    000
  • Java Stream中利用函数式参数实现数据去重与过滤

    Java Stream中利用函数式参数实现数据去重与过滤Java Stream中利用函数式参数实现数据去重与过滤Java Stream中利用函数式参数实现数据去重与过滤Java Stream中利用函数式参数实现数据去重与过滤

    本文深入探讨了java stream api中`filter`方法与函数式参数的结合使用。通过分析一个具体的代码示例,我们将理解如何利用`predicate`和`function`来高效地从集合中筛选出基于特定属性的唯一元素,并揭示了其内部执行机制,包括`predicate`的生成与逐元素应用过程,…

    2026年10月28日 • 用户投稿
    000
  • JavaScript视频流媒体开发

    JavaScript视频流媒体开发JavaScript视频流媒体开发JavaScript视频流媒体开发JavaScript视频流媒体开发

    JavaScript通过MSE、WebRTC和EME等API实现视频流媒体核心功能。1. MSE支持动态加载分段视频,用于HLS或DASH自适应码率传输;2. 播放器库如hls.js和dash.js简化开发并提升兼容性;3. WebRTC实现低延迟实时通信,适用于互动直播;4. EME集成DRM系统…

    2026年10月28日 • 用户投稿
    100
  • 解决LWJGL中GLFW创建窗口引发的JNI版本错误与原生崩溃

    解决LWJGL中GLFW创建窗口引发的JNI版本错误与原生崩溃解决LWJGL中GLFW创建窗口引发的JNI版本错误与原生崩溃解决LWJGL中GLFW创建窗口引发的JNI版本错误与原生崩溃解决LWJGL中GLFW创建窗口引发的JNI版本错误与原生崩溃

    本文针对lwjgl和glfw开发中遇到的“unsupported jni version detected”警告及随后的原生崩溃问题,深入分析了`glfwcreatewindow`函数第四个参数误用导致的核心原因。教程详细阐述了该参数的正确用法,并提供了修复方案,旨在帮助开发者避免此类因api参数混…

    2026年10月28日 • 用户投稿
    200
  • 在Node.js Webpack打包的AWS Lambda中正确读取环境变量

    在Node.js Webpack打包的AWS Lambda中正确读取环境变量在Node.js Webpack打包的AWS Lambda中正确读取环境变量在Node.js Webpack打包的AWS Lambda中正确读取环境变量在Node.js Webpack打包的AWS Lambda中正确读取环境变量

    在node.js webpack打包的aws lambda函数中,`process.env`可能返回`null`值,这通常是由于webpack配置不当或代码中存在变量遮蔽导致的。本文将详细介绍如何通过优化webpack配置(如使用`defineplugin`或`environmentplugin`)…

    2026年10月28日 • 用户投稿
    000
  • Dart中将Firebase数据高效转换为POJO类:实践与技巧

    Dart中将Firebase数据高效转换为POJO类:实践与技巧Dart中将Firebase数据高效转换为POJO类:实践与技巧Dart中将Firebase数据高效转换为POJO类:实践与技巧Dart中将Firebase数据高效转换为POJO类:实践与技巧

    本教程详细指导如何在dart应用中将从firebase获取的原始map数据安全、高效地转换为强类型的pojo(plain old java object)类。我们将探讨`fromjson`构造方法的最佳实践,解决类型转换中的常见问题,并提供清晰的代码示例,以提升数据处理的健壮性和可维护性。 在Dar…

    2026年10月28日 • 用户投稿
    100
  • 如何使用SnakeYAML将复杂YAML结构映射到Java对象

    如何使用SnakeYAML将复杂YAML结构映射到Java对象如何使用SnakeYAML将复杂YAML结构映射到Java对象如何使用SnakeYAML将复杂YAML结构映射到Java对象如何使用SnakeYAML将复杂YAML结构映射到Java对象

    本文旨在指导读者如何利用SnakeYAML库将复杂的YAML文件内容直接映射到强类型的Java对象中,从而避免手动解析嵌套的`LinkedHashMap`和`ArrayList`,实现更简洁、类型安全的数据访问。通过定义与YAML结构对应的POJO类,您可以轻松地加载并操作多层嵌套的数据,如从配置列…

    2026年10月28日 • 用户投稿
    600
  • 使用正则表达式验证Slug,允许斜杠

    使用正则表达式验证Slug,允许斜杠使用正则表达式验证Slug,允许斜杠使用正则表达式验证Slug,允许斜杠使用正则表达式验证Slug,允许斜杠

    本文介绍了如何使用正则表达式验证Slug,并允许Slug中包含斜杠。通过修改分隔符表达式,使正则表达式能够匹配包含斜杠的Slug,并提供代码示例进行验证。 Slug通常用于URL中,作为人类可读且对SEO友好的标识符。一个常见的Slug格式是字母数字字符的组合,用连字符分隔。然而,有时我们需要允许S…

    2026年10月28日 • 用户投稿
    000
  • Chromium Android UI 组件构成详解

    Chromium Android UI 组件构成详解Chromium Android UI 组件构成详解Chromium Android UI 组件构成详解Chromium Android UI 组件构成详解

    本文旨在帮助开发者快速定位并理解 Chromium Android 版本的 UI 组件构成。通过分析 `chrome/android/java/res_app/layout/main.xml` 文件以及 `chrome/android/java/res/layout` 目录下的其他 XML 文件,开…

    2026年10月28日 • 用户投稿
    000
  • 修复JavaScript中字符串编码问题的实用指南

    修复JavaScript中字符串编码问题的实用指南修复JavaScript中字符串编码问题的实用指南修复JavaScript中字符串编码问题的实用指南修复JavaScript中字符串编码问题的实用指南

    当从外部系统接收到编码损坏的字符串时,javascript开发者常面临挑战。本文将深入探讨一种常见的字符串编码错误(utf-8字节被误解为unicode字符),解释为何直接使用`textdecoder`可能无效,并提供一个基于`escape()`和`decodeuricomponent()`的可靠解…

    2026年10月28日 • 用户投稿
    000
  • JavaScript 中统计对象数组中唯一属性值的数量(包含 Null 值)

    JavaScript 中统计对象数组中唯一属性值的数量(包含 Null 值)JavaScript 中统计对象数组中唯一属性值的数量(包含 Null 值)JavaScript 中统计对象数组中唯一属性值的数量(包含 Null 值)JavaScript 中统计对象数组中唯一属性值的数量(包含 Null 值)

    本文介绍了如何使用 JavaScript 统计对象数组中特定属性的唯一值数量,并着重处理了 `null` 值的特殊情况,即每个 `null` 值都应被视为一个独立的唯一值。通过 `reduce` 方法和缓存机制,高效地实现了这一目标。 在处理 JavaScript 对象数组时,经常需要统计特定属性的…

    2026年10月28日 • 用户投稿
    100

发表回复

登录后才能评论
关注微信