You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript工厂函数:键数组动态返回类型与无限递归错误排查

解决TypeScript递归类型过深导致的「Type instantiation is excessively deep」错误

场景与需求

我在AWS Lambda函数中使用UserModule、NotificationsModule、CompanyModule等模块,定义了如下Modules接口:

interface Modules {
    company: ICompanyModule
    user: {
        base: IUserModule
    }
    notifications: {
        settings: INotificationSettingsModule
        details: INotificationDetailsModule
    }
}

为减少手动导入和实例化的工作量,我编写了factory方法,期望根据传入的Modules接口键(支持嵌套路径,如user.base)返回仅包含所需模块的对象,示例如下:

// 单个模块
const module = factory('company') // 类型应为ICompanyModule

// 多个模块
const modules = factory(['company', 'user.base']) // 类型应为{ company: ICompanyModule, 'user.base': IUserModule }

问题描述

我编写了Path、PathValue、ReturnObject等类型定义,调用factory时能得到正确的对象结构,但TypeScript在ReturnObject的{ [Key in P[number]]: ... }处抛出Type instantiation is excessively deep and possibly infinite错误。推测是P可扩展为Array<Path<T>>导致递归过深,求解决该错误的方法。

附当前类型定义代码:

// 获取Modules接口的所有键及嵌套键
type Path<T, Key extends keyof T = keyof T> = Key extends string
    ? T[Key] extends Record<string, unknown>
        ?
            | `${Key}.${Path<T[Key], Exclude<keyof T[Key], keyof Array<unknown>>> & string}`
            | `${Key}.${Exclude<keyof T[Key], keyof Array<unknown>> & string}`
            | Key
        : Key
    : never

// 获取Path对应的类型值
type PathValue<T, P extends Path<T>> = P extends `${infer Key}.${infer Rest}`
    ? Key extends keyof T
        ? Rest extends Path<T[Key]>
            ? PathValue<T[Key], Rest>
            : never
        : never
    : P extends keyof T
        ? T[P]
        : never

// 根据单个键或键数组构建返回对象类型
type ReturnObject<T extends Object, P extends Path<T> | Array<Path<T>>> = P extends Array<Path<T>>
    ? {
        [Key in P[number]]: Key extends keyof T
            ? T[Key] extends Object
                ? Key extends Path<T[Key]>
                    ? ReturnObject<T[Key], Key>
                    : T[Key]
                : never
            : Key extends `${infer K}.${infer Rest}`
                ? K extends keyof T
                    ? T[K] extends Object
                        ? Rest extends Path<T[K]>
                            ? ReturnObject<T[K], Rest>
                            : never
                        : never
                    : never
                : never
    }
    : P extends keyof T
        ? T[P]
        : P extends `${infer K}.${infer Rest}`
            ? K extends keyof T
                ? T[K] extends Object
                    ? Rest extends Path<T[K]>
                        ? ReturnObject<T[K], Rest>
                        : never
                    : T[K]
                : never
            : never

// factory函数声明
declare function factory<T extends ModuleFactory, P extends Path<T> | Array<Path<T>>>(
    modules: P
): ReturnObject<T, P>

解决方案

核心优化:复用PathValue避免重复递归

ReturnObject的问题在于处理数组路径时,重复调用自身递归解析路径,导致TypeScript类型计算栈溢出。可以直接复用已实现的PathValue类型,简化ReturnObject的逻辑,彻底消除不必要的递归嵌套:

修改后的ReturnObject类型:

type ReturnObject<T extends object, P extends Path<T> | Array<Path<T>>> = 
  P extends Array<Path<T>> 
    ? { [K in P[number]]: PathValue<T, K> }
    : PathValue<T, P>;

优化说明

  • 当传入路径数组时,直接遍历数组中的每个路径,通过PathValue获取对应的类型,不再触发ReturnObject自身的递归调用,大幅降低类型计算的深度。
  • 单个路径的情况直接复用PathValue的结果,逻辑更简洁,同时保证类型正确性。

额外优化:简化Path类型(可选)

原Path类型生成了冗余的路径组合,进一步增加了类型计算压力。可以简化为以下版本,只生成必要的路径:

type Path<T> = 
  T extends object 
    ? { [K in keyof T & string]: 
        | K 
        | `${K}.${Path<T[K]>}` 
      }[keyof T & string]
    : never;

这个版本的Path会递归生成所有层级的路径(如company、user、user.base等),避免原版本中重复的中间路径定义,进一步提升类型计算效率。

验证效果

修改后,调用示例的类型推导完全符合预期:

  • factory('company')返回类型为ICompanyModule
  • factory(['company', 'user.base'])返回类型为{ company: ICompanyModule, 'user.base': IUserModule }
    同时TypeScript不再抛出递归过深的错误。

内容的提问来源于stack exchange,提问作者Ben Young

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 07:35:39