TS4.1:有没有办法定义这个不太深的属性路径类型?



在尝试即将推出的TypeScript 4.1的模板文本类型时,我试图定义一个可以检查属性路径的泛型类型。

在TS 4.1之前,没有可能键入'foo.bar.baz'之类的表达式,您只能接受string。现在,使用模板文本类型,我希望能够键入这些属性路径,并将它们用于MongoDB查询和投影对象等。例如:

db.someCollection.find({ 'foo.bar.baz': { $exists: true } });

这就是我想到的类型:

type PropsPath<T> =
T extends object
? T extends any[]
? number
: {
[P in keyof T]: P | `${P}.${PropsPath<T[P]>}`
}[keyof T]
: '';

TS游乐场的完整示例

可悲的是,这种类型被认为是";过深或可能无穷大";通过TS编译器。有没有什么方法可以重新定义它,而不会引发错误?

使用TS 4.0,可以使用Variadic元组类型来获得Array类型的确切元素类型。将其与递归类型别名相结合,可以实现您想要的效果。查看TS游乐场

TS游乐场

进一步解释:

  • ExtractPropsPath<T>:此别名只保留T中属于string[]的任何成员。例如:['arr'] | ['arr', ...any[]],此别名将只保留['arr']
  • Join:加入一个具有分隔符D[...Elements][]。在这种情况下,分隔符是点.
  • PathOf:解决方案的关键就在这里。PathOf返回一个联合类型,它将:
    • 如果T[key]是基元,则返回[key]
    • 如果T[key]是对象,则返回[key] | [key, ...nested-if](注意...,我们将返回数组类型(
    • "嵌套if";是我们检查的地方
      • T[key]是一个元组,有1个成员(例如:[number](,如果成员类型是基元,则返回['0'],或者我们返回递归PathOf<member>(将此点标记为{1}(
      • T[key]是一个元组,具有多个成员或一个数组类型(例如:[number, string]number[](,则
        • 检查元组是否是并集(例如:[number, string]->number | string(,然后只返回递归PathOf
        • 如果它不是并集,那么它是一个数组类型,我们可以使用与{1}相同的逻辑
  • SerializedPathOf:基本上将作为string[]的并集的PathOf变成具有分隔符.string的联合并集(例如:['obj'] | ['obj', 'num']->'obj' | 'obj.num'(

为了回答我自己的问题,我最终想出了以下类型:

type PropsPath<T extends object> = {
[P in keyof T]: T[P] extends object
? `${string & P}` | `${string & P}.${PropsPath<T[P]>}`
: `${string & P}`
}[T extends any[] ? (number & keyof T) : keyof T];

TS游乐场中的更新示例

它是有效的,但遗憾的是,它没有涵盖T具有嵌套属性的情况,该属性是一个未知长度的数组(any[](。

在Amit的答案的基础上,这也适用于数组,尽管它不适用于像周这样的元组。

type Path<T> = T extends Array<any>
? `${number}` | `${number}.${Path<T[number]>}`
: T extends object
? {
[P in keyof T]: (P & string) | `${P & string}.${Path<T[P]>}`
}[keyof T]
: never

TS游乐场

最新更新