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
相关产品推荐
相关产品推荐

