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

TypeScript:降低深层属性选择类型的计算复杂度

TypeScript深度属性选择类型的性能优化求助

我正在开发一个库的API,用TypeScript类型实现从对象中推断键路径字符串,并根据路径选择对应属性。已基于Stack Overflow的思路实现功能:

给定接口:

interface Obj {
  prop0: string;
  prop1: {
    prop2: {
      prop3: {prop4: "I am excluded"; prop5: string}[]
    };
    prop6: "I am excluded";
  };
  prop7: "I am excluded";
}

调用getAttributes(["prop0", "prop1.prop2.prop3[0].prop5"])会返回如下类型:

{
  prop0: string;
  prop1: {
    prop2: {
      prop3: [{prop5: string}?]
    }
  }
}

该类型能满足从数据库实体中仅返回指定属性、减少网络传输体积的需求,但计算负担过重:结合交叉类型/联合类型使用时,VSCode等IDE的语言服务器常出现卡顿甚至崩溃。

现有实现代码:

type Divider<T> = T extends unknown[] ? `[${number}]` : ".";

type Paths<T> = T extends Record<string, unknown>
  ? {[K in keyof T]: `${Exclude<K, symbol>}${"" | `${Divider<T[K]>}${Paths<T[K]>}`}`}[keyof T]
  : T extends (infer X)[]
    ? "" | `${Divider<X>}${Paths<X>}`
    : never;

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

type DeepPick<TObject, TKey extends string> = UnionToIntersection<TObject extends object
  ? TKey extends `${infer Head}.${infer Tail}` | `${infer Head}[${number}].${infer Tail}`
    ? {
      [P in Head & keyof TObject]: TObject[P] extends (infer A)[]
        ? [DeepPick<A, Tail>?]
        : DeepPick<TObject[P], Tail>
    }
    : TKey extends `${infer Head}[${number}]`
      ? {
        [P in Head & keyof TObject]: TObject[P] extends (infer A)[] ? [A?] : never
      }
      : TKey extends keyof TObject
        ? Pick<TObject, TKey>
        : never
  : TObject>;

declare function getAttributes<T, P extends Paths<T>>(attributes: P[]): DeepPick<T, P>;

实际场景中,该逻辑是数据库实体的getItem方法,签名近似getItem<P extends Paths<Entity>>(key: Key<Entity>, attributes?: P[]): DeepPick<Entity, P>,会向Paths和DeepPick传递更多计算类型与条件逻辑。当前功能正常,但编译器负载过高,寻求优化方案降低计算复杂度。


优化方案建议

1. 合并条件分支,减少重复类型检查

原DeepPick中存在多轮嵌套条件判断,可将数组路径与对象路径的处理逻辑合并,减少infer的触发次数:

// 提取路径解析的公共逻辑
type ParsePathSegment<T extends string> = 
  T extends `${infer Head}[${number}]` ? Head : T;

type GetPathTail<T extends string> = 
  T extends `${string}.${infer Tail}` ? Tail : 
  T extends `${string}[${number}].${infer Tail}` ? Tail : never;

type IsArrayPath<T extends string> = T extends `${string}[${number}]` ? true : false;

2. 避免UnionToIntersection的过度依赖

原实现通过将每个路径的Pick结果转为联合再转交叉来合并类型,可改为直接对路径数组进行累加式处理,避免高阶交叉类型的计算开销:

// 改为针对路径数组的累加Pick,而非联合转交叉
type DeepPickArray<T, P extends string[]> = 
  P extends [infer First extends string, ...infer Rest extends string[]]
    ? Merge<DeepPick<T, First>, DeepPickArray<T, Rest>>
    : {};

// 简单的类型合并工具
type Merge<A, B> = A & B extends infer O ? { [K in keyof O]: O[K] } : never;

3. 简化Paths类型的递归逻辑

原Paths会生成所有可能的路径,若业务中不需要全量路径校验,可限制递归深度,或改为仅支持显式传入的路径格式而非自动生成全量路径:

// 限制递归深度的Paths(示例限制为3层)
type Paths<T, Depth extends number = 3> = 
  Depth extends 0 ? never :
  T extends Record<string, unknown>
    ? {[K in keyof T]: `${Exclude<K, symbol>}` | `${Exclude<K, symbol>}${Divider<T[K]>}${Paths<T[K], Depth-1>}`}[keyof T]
    : T extends (infer X)[]
      ? "" | `${Divider<X>}${Paths<X, Depth-1>}`
      : never;

4. 提取公共子类型,利用编译器缓存

将重复使用的类型判断(如数组类型检查)提取为独立类型,让TypeScript编译器缓存这些结果,减少重复计算:

type IsArray<T> = T extends unknown[] ? true : false;
type ArrayElement<T> = T extends (infer A)[] ? A : never;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 12:25:17