如何匹配打字稿中的嵌套键

Iro*_*ean 6 typescript

我已经创建了一个nameOf用于打字稿的简单助手。

function nameOf<T>(name: Extract<keyof T, string>) {
  return name;
}
Run Code Online (Sandbox Code Playgroud)

在功能需要代表一个属性的值的字符串,而没有适当类型的,我可以用它的地方,像这样:expectsKey(nameOf<MyObject>("someMyObjectProperty"))。这意味着即使我不控制expectsKey(key: string)我也可以对传递给它的字符串进行某种类型检查。这样,如果MyObject重命名on属性,则该nameOf()调用将显示一个错误,普通函数在执行之前不会检测到。

是否可以将其扩展到嵌套元素?

即,进行某种nameOf<MyComplexObject>('someProperty[2].someInnerProperty')类型检查以确保它与类型的结构匹配的一种方法MyComplexObject

Ger*_*it0 7

直接地?不可以。您不能创建连接属性来在 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']
Run Code Online (Sandbox Code Playgroud)

你不能在你的nameOf函数中直接使用它。这是一个错误,因为类型实例化将被检测为可能是无限的。

declare function nameOf<T>(path: NamesOf<T>): string
Run Code Online (Sandbox Code Playgroud)

但是,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
Run Code Online (Sandbox Code Playgroud)

如果您使用另一条路线并将路径包含在通用约束中,请确保将路径标记为默认路径(因此您在使用该函数时不必指定它)和扩展NameOf<T>(以便用户nameOf不能在钥匙上撒谎)

declare function nameOf<T, P extends NamesOf<T> = NamesOf<T>>(path: P): string
Run Code Online (Sandbox Code Playgroud)