为何该TypeScript条件类型静态有效但动态调用失效?
问题成因
动态调用时类型校验失败的核心原因是TypeScript对testObject的默认类型推断行为:
- 直接声明
const testObject = { arr: ['a', 'b', 'c'] }时,TypeScript会把arr的类型推断为宽数组string[],而非精确的元组['a', 'b', 'c']。 - 你的
ArrayIndices类型依赖数组的length属性为具体字面量数字(元组的length是固定字面量)才能递归生成索引联合类型;但普通数组的length是number类型,无法触发递归终止条件,最终ArrayIndices<string[]>会返回never。 - 因此
Path<{ arr: string[] }>的数组分支中,${Key & string}[${ArrayIndices<T[Key]>}]会变成never,仅剩下"arr"这一个有效路径,导致传入'arr[0]'时类型不匹配。
可行解决办法
办法1:使用as const断言强制推断元组类型
通过as const让TypeScript把数组推断为只读元组,保留精确的长度和元素类型:
const testObject = { arr: ['a', 'b', 'c'] } as const; const testFunction = <T, P extends Path<T>>(obj: T, path: P) => {}; testFunction(testObject, 'arr[0]'); // 类型校验通过
注意:as const会把数组设为只读类型,若后续需要修改数组元素,可结合Readonly<T>或手动调整类型。
办法2:手动指定testObject的元组类型
显式声明arr为元组类型,避免TypeScript自动推断为宽数组:
const testObject: { arr: ['a', 'b', 'c'] } = { arr: ['a', 'b', 'c'] }; testFunction(testObject, 'arr[0]'); // 类型校验通过
这种方式既保留了元组的精确类型,也不限制数组的可写性,灵活性更高。
办法3:修改ArrayIndices兼容普通数组(牺牲精确性换通用性)
如果需要支持长度不固定的动态数组,可修改ArrayIndices,当数组长度为number时返回number类型,让模板字符串匹配任意数字索引:
// 拆分递归逻辑,兼容元组和普通数组 type _ArrayIndices< N extends any[], Acc extends number[] = [] > = Acc['length'] extends N['length'] ? Acc[number] : _ArrayIndices<N, [...Acc, Acc['length']]>; type ArrayIndices<N extends any[]> = N extends { length: infer L } ? L extends number ? number extends L // 判断是否为普通数组(length为number而非字面量) ? number : _ArrayIndices<N> : never : never; // 原Path类型无需修改 type Path<T, Key = keyof T> = Key extends keyof T ? T[Key] extends unknown[] ? `${Key & string}[${ArrayIndices<T[Key]>}]` | `${Key & string}` : T[Key] extends object ? T[Key] extends infer R ? `${Key & string}.${Path<R, keyof R>}` | `${Key & string}` : never : `${Key & string}` : never; const testObject = { arr: ['a', 'b', 'c'] }; // 依旧是string[]类型 testFunction(testObject, 'arr[0]'); // 类型校验通过 testFunction(testObject, 'arr[100]'); // 也会通过,因为索引被推断为number类型
这种方式的 trade-off 是失去了精确的索引范围校验,但能兼容动态数组场景。
内容的提问来源于stack exchange,提问作者Joseph Beuys' Mum
相关产品推荐
相关产品推荐

