TypeScript模板字面量类型中数组索引排除逻辑的疑问
为什么
Exclude<keyof T[Key], keyof any[]>没有移除数组的索引键? 我在学习TypeScript模板字面量类型时,发现了一个生成对象所有可能键路径的类型。其中PathImpl类型里使用了Exclude<keyof T[Key], keyof any[]>,我原本以为keyof any[]包含数组的所有方法以及0、1、2等索引,执行排除操作后应该会移除T[Key]的索引键,但实际情况并非如此。相关类型代码如下:
type PathImpl<T, Key extends keyof T> = Key extends string ? T[Key] extends Record<string, any> ? `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof any[]>> & string}` | `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}` : never : never; type PathImpl2<T> = PathImpl<T, keyof T> | keyof T; type Path<T> = PathImpl2<T> extends string | keyof T ? PathImpl2<T> : keyof T;
问题根源:keyof any[]的实际构成与预期不符
你对keyof any[]的理解存在偏差:keyof any[]并不包含字符串形式的数字索引(比如"0"、"1"),它实际是number | keyof Array<any>:
number:数组的数字索引类型(比如访问arr[0]时的0是number类型);keyof Array<any>:数组的内置方法和属性的字符串键(比如"push"、"length"、"map"等)。
这就导致两种不符合预期的情况:
- 如果
T[Key]是标准数组:keyof T[Key]等于number | keyof Array<any>,和keyof any[]完全一致,所以Exclude<keyof T[Key], keyof any[]>会得到never——数字索引已经被移除,但模板字面量会自动把number类型的索引转为字符串(比如0变成"0"),让你误以为索引还存在; - 如果
T[Key]是类数组对象(带字符串数字索引):比如{ "0": { foo: "bar" }, "1": { baz: "qux" } },keyof T[Key]是"0" | "1" | ...,这些字符串数字键不在keyof any[]的范围内,所以Exclude根本不会移除它们。
解决方案:精准排除数字类键
如果你想移除所有数字类型索引和字符串形式的数字索引,可以自定义一个类型来过滤这类键:
// 过滤掉数字类型和可转为数字的字符串键 type ExcludeNumberKeys<K> = K extends number ? never : K extends `${number}` ? never : K; // 修改后的PathImpl类型 type PathImpl<T, Key extends keyof T> = Key extends string ? T[Key] extends Record<string, any> ? `${Key}.${PathImpl<T[Key], ExcludeNumberKeys<keyof T[Key]>> & string}` | `${Key}.${ExcludeNumberKeys<keyof T[Key]> & string}` : never : never; type PathImpl2<T> = PathImpl<T, keyof T> | keyof T; type Path<T> = PathImpl2<T> extends string | keyof T ? PathImpl2<T> : keyof T;
这个ExcludeNumberKeys类型会:
- 直接排除
number类型的键(数组的数字索引); - 排除所有可以解析为数字的字符串键(比如
"0"、"123"这类类数组对象的索引)。
这样就能达到预期的“移除数组/类数组对象的索引键”的效果。
内容的提问来源于stack exchange,提问作者lefty
相关产品推荐
相关产品推荐

