如何在链式类型构建场景下提升TypeScript性能?
TypeScript链式泛型类型的性能优化方案
你提供的代码实现了一个逐步构建的链式泛型结构,但多层链式调用会触发TypeScript的Type instantiation is excessively deep and possibly infinite.(2589)错误。以下是不牺牲功能前提下的性能优化措施:
原代码
type LMerge<T1, T2> = { [k in keyof T1]: k extends keyof T2 ? T2[k] : T1[k] } type PossibleGenerics = { Output?: unknown Error?: unknown ResultResolverController?: unknown ErrorResolverController?: unknown } type Generics = Required<PossibleGenerics> type NodeItem<T extends Generics, Defaults extends Generics> = { < S extends PossibleGenerics = {}, NewDefaults extends PossibleGenerics = {}, UpdatedDefaults extends Generics = LMerge<Defaults, NewDefaults>, Child extends Generics = LMerge<UpdatedDefaults, S>, >(): NodeItem<Child, UpdatedDefaults> NodeItem: T } const example = {} as NodeItem< { Output: string Error: string ResultResolverController: string ErrorResolverController: string }, Generics > const exampleUsage1 = example<{ Output: number }, { Error: Error }>() const exampleUsage2 = exampleUsage1<{ Output: number }>() // ... const exampleUsage21 = exampleUsage20<{ Output: number }>() // Type instantiation is excessively deep and possibly infinite.(2589)
优化措施
1. 移除冗余的中间泛型参数
原代码中UpdatedDefaults和Child是推导而来的中间泛型,TypeScript每次处理都会重复计算这些类型,增加检查开销。直接在返回类型中合并计算,减少泛型参数数量:
// 更高效的合并实现:先移除T1中与T2重叠的键,再合并T2 type LMerge<T1, T2> = Omit<T1, keyof T2> & T2; type NodeItem<T extends Generics, Defaults extends Generics> = { <S extends PossibleGenerics = {}, NewDefaults extends PossibleGenerics = {}>(): NodeItem< LMerge<LMerge<Defaults, NewDefaults>, S>, LMerge<Defaults, NewDefaults> >; NodeItem: T; };
2. 替换动态映射合并为显式键合并
针对Generics的固定结构,直接显式合并已知键,避免遍历所有键的性能损耗:
type LMerge<T extends Generics, U extends PossibleGenerics> = { Output: U['Output'] extends undefined ? T['Output'] : U['Output']; Error: U['Error'] extends undefined ? T['Error'] : U['Error']; ResultResolverController: U['ResultResolverController'] extends undefined ? T['ResultResolverController'] : U['ResultResolverController']; ErrorResolverController: U['ErrorResolverController'] extends undefined ? T['ErrorResolverController'] : U['ErrorResolverController']; };
这种方式跳过了动态映射类型的遍历逻辑,TypeScript处理起来更高效。
3. 添加递归深度限制
引入可选的Depth泛型参数,限制递归实例化的最大深度,避免触发过深错误:
type NodeItem<T extends Generics, Defaults extends Generics, Depth extends number = 20> = { <S extends PossibleGenerics = {}, NewDefaults extends PossibleGenerics = {}>(): Depth extends 0 ? NodeItem<T, Defaults> : // 深度耗尽时保持当前类型 NodeItem< LMerge<LMerge<Defaults, NewDefaults>, S>, LMerge<Defaults, NewDefaults>, // 通过预定义数组实现深度递减 [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19][Depth] >; NodeItem: T; };
默认设置深度为20,足以覆盖绝大多数使用场景,同时避免无限递归。
4. 延迟类型计算到属性访问时
将类型合并逻辑从函数返回类型移到NodeItem属性上,仅在访问该属性时才触发类型计算,减少即时推导负担:
type NodeItem<T extends Generics, Defaults extends Generics> = { <S extends PossibleGenerics = {}, NewDefaults extends PossibleGenerics = {}>(): NodeItem<any, LMerge<Defaults, NewDefaults>>; // 延迟计算最终类型,仅在访问时解析 get NodeItem(): LMerge<Defaults, T>; };
函数返回时用any占位,直到访问NodeItem属性才合并出最终类型,大幅降低递归类型的即时计算开销。
内容的提问来源于stack exchange,提问作者TrevTheDev
相关产品推荐
相关产品推荐

