TypeScript 实战,如何推导出嵌套对象类型的字符串路径

427 阅读1分钟

效果

根据嵌套对象类型推导出可能存在的路径

type A = DeepPath<{
  id: string
  name: string
}> // 'id' | 'name'

type B = DeepPath<{
  id: string
  info: {
    name: string
    phone: string
  }
}> // 'id' | 'info' | 'info.name' | 'info.phone'

type C = DeepPath<{
  id: string
  children: Array<{
    name: string
    phone: string
  }>
}> // 'id' | 'children' | `children[${number}]` | `children[${number}].name` | `children[${number}].phone`

如何推导

对于不是嵌套对象的类型可以通过 keyof 获取对象类型的键值,但这样无法处理深层类型

type A = keyof {
  id: string
  name: string
} // 'id' | 'name'

对于嵌套对象类型,需要要遍历嵌套对象类型的每一个键值的类型。然后如果遍历类型是对象或者数组类型就根据情况运用模板字符串类型拼接增加 . 或者 [${number}]

推导过程

  • 通过模板字符串类型拼接路径 (这里将对象或者数组类型一起处理,也可以分开)
type NestedPath<T extends 'array' | 'object', P, C = undefined> = `${P & string}${T extends 'array' ? `[${number}]` : ''}${C extends string ? `.${C}` : ''}`

提示

  1. 由于模板字符串类型是建立在文本类型上的,所以需要使用 P & string
  2. T extends 'array' ? 'a' : 'b' 是条件类型,根据条件匹配对应类型
  • 处理不同类型
type DeepNested<V, K = ''> = V extends object[]
  ? NestedPath<'array', K, DeepPath<V[number]> | undefined>
  : V extends unknown[]
  ? NestedPath<'array', K>
  : V extends object
  ? NestedPath<'object', K, DeepPath<V>>
  : never

提示

  1. V extends object[] 判断 V 是不是基于对象数组或者对象元组类型
  2. V[number] 提取对象数组或者对象元组类型中的对象类型
  3. NestedPath<'array', K, DeepPath<V[number]> | undefined> 其中的 | undefined 是为了生成 K[${number}] 类型
  • 完善 DeepPath 类型
type DeepPath<T extends object> = {
  [Q in keyof T]-?: Q | DeepNested<NonNullable<T[Q]>, Q>
}[keyof T]

提示

type A<T extends object> = {
  [Q in keyof T]: T[Q]
}

与泛型 T 相同,相当于遍历泛型 T。这里 T[Q] 相当于遍历时进行的类型转化

type B<T extends object> = {
  [Q in keyof T]: T[Q]
}[keyof T]

这里提取遍历后的类型值

  1. 为了排除 undefined 等类型影响类型的推导,通过 [Q in keyof T]-?:NonNullable<T[Q]> 排除

到此推导就结束了,通过 DeepPath 就可以获取嵌套对象类型的字符串路径

完整代码