TypeScript如何实现已知键名未知路径的深层属性类型提取
原理解析
你的实现本质是深度优先递归遍历对象的所有属性路径,匹配目标键后返回对应类型,最后合并所有匹配结果。关于你疑惑的[keyof T]逻辑,拆解如下:
- 第一层条件判断
Key extends keyof T优先匹配当前层级的目标键,如果命中直接返回对应类型,终止当前路径的递归。 - 如果当前层级没有匹配到目标键,会构造一个临时映射类型:遍历当前对象的所有属性,对每个属性值做判断:
- 如果属性值是对象类型,就递归调用
GetDeepProp深入查找目标键 - 如果属性值不是对象类型,说明这条路径不可能存在目标键,返回
unknown
- 如果属性值是对象类型,就递归调用
- 末尾的
[keyof T]是索引访问操作,会把上述临时映射类型所有属性值的类型取联合。你的示例能正常运行,是因为示例里的所有非目标路径要么不存在,要么全部是嵌套对象最终命中目标键,没有返回unknown的分支干扰结果。
现有实现的隐藏缺陷
你的实现仅在「所有非目标路径都是嵌套对象最终命中目标键」的场景下可用,实际存在几个明显问题:
- 如果对象存在不包含目标键的非对象属性,最终返回类型会变成
unknown。比如以下示例会直接失效:
原因是type Test = { name: string; account: { region: number } } // 实际返回 unknown,预期返回 number type TestRegion = GetDeepProp<Test, 'region'>string | unknown的联合类型结果是unknown,直接覆盖了有效匹配结果。 - 不支持数组类型的嵌套场景:如果中间路径是数组,你的实现会返回
unknown,无法匹配数组元素内的目标键。 - 目标键不存在时没有明确的错误提示,只会返回
unknown,不利于类型校验。 - 如果多个路径都存在相同的目标键,会返回所有匹配类型的联合,如果你预期仅存在一个匹配路径,这种结果不符合预期。
更优的实现方案
核心优化点是把无匹配路径的返回值从unknown换成never(联合类型中never会被自动消去,不会干扰有效结果),同时增加数组类型的兼容逻辑:
type GetDeepProp<T extends object, Key extends string> = Key extends keyof T ? T[Key] : { [k in keyof T]: T[k] extends object ? T[k] extends (infer Elem)[] ? GetDeepProp<Elem, Key> : GetDeepProp<T[k], Key> : never }[keyof T] // 缺陷场景修复验证 type Test = { name: string; account: { region: number } } type TestRegion = GetDeepProp<Test, 'region'> // 正确返回 number type ArrayTest = { accounts: { region: "USA" | "China" }[] } type ArrayRegion = GetDeepProp<ArrayTest, 'region'> // 正确返回 "USA" | "China" type NonExistKey = GetDeepProp<Test, 'none'> // 不存在的键返回 never,便于校验
如果需要限制递归深度避免无限循环,可以额外增加一个深度控制参数,默认设为3或4层即可覆盖绝大多数业务场景。
内容的提问来源于stack exchange,提问作者Gisheri
相关产品推荐
相关产品推荐

