直接地?不可以。您无法创建连接属性来在 TS 中创建新字符串,而这是此功能所必需的。
但是,您可以通过映射类型获得类似的功能。
interface MyObject {
prop: {
arr?: { inner: boolean }[]
other: string
method(): void
}
}
// Creates [A, ...B] if B is an array, otherwise never
type Join<A, B> = B extends any[]
? ((a: A, ...b: B) => any) extends ((...args: infer U) => any) ? U : never
: never
// Creates a union of tuples descending into an object.
type NamesOf<T> = {
[K in keyof T]: [K] | Join<K, NamesOf<NonNullable<T[K]>>>
}[keyof T]
// ok
const keys: NamesOf<MyObject> = ['prop']
const keys2: NamesOf<MyObject> = ['prop', 'arr', 1, 'inner']
// error, as expected
const keys3: NamesOf<MyObject> = [] // Need at least one prop
const keys4: NamesOf<MyObject> = ['other'] // Wrong level!
// Technically this maybe should be allowed...
const keys5: NamesOf<MyObject> = ['prop', 'other', 'toString']
您不能直接在您的nameOf
功能。这是一个错误,因为类型实例化将被检测为可能是无限的。
declare function nameOf<T>(path: NamesOf<T>): string
但是,您可以使用NamesOf
如果你让 TypeScript 推迟其解析,直到你实际使用该函数。您可以相当轻松地做到这一点,方法是将其作为通用默认值,或者将参数类型包装在条件中(这提供了防止使用nameOf
当类型是原始类型时)
interface MyObject {
prop: {
arr?: { inner: boolean }[]
other: string
method(): void
},
prop2: {
something: number
}
}
// Creates [A, ...B] if B is an array, otherwise never
type Join<A, B> = B extends any[]
? ((a: A, ...b: B) => any) extends ((...args: infer U) => any) ? U : never
: never
// Creates a union of tuples descending into an object.
type NamesOf<T> = {
[K in keyof T]: [K] | Join<K, NamesOf<NonNullable<T[K]>>>
}[keyof T]
declare function nameOf<T>(path: T extends object ? NamesOf<T> : never): string
const a = nameOf<MyObject>(['prop', 'other']) // Ok
const c = nameOf<MyObject>(['prop', 'arr', 3, 'inner']) // Ok
const b = nameOf<MyObject>(['prop', 'something']) // Error, should be prop2
如果您采用其他路线并将路径包含在通用约束中,请务必将该路径标记为默认路径(这样您在使用该函数时不必指定它)和扩展路径NameOf<T>
(以便用户nameOf
不能对钥匙撒谎)
declare function nameOf<T, P extends NamesOf<T> = NamesOf<T>>(path: P): string