在尝试即将推出的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游乐场