如何根据 union/intersection 对象类型的值创建类型?

How to make a type from the values of an union/intersection object type?

我有一个困难的类型,想知道如何将类型定义为预定义对象类型中所有可能值的联合。

假设我们有一个自动生成的类型 Person 如下所示:

type Person = {
  favouriteColor: string
  age: number
  female: boolean
}

如何使用 Person 类型创建等于 string | number | boolean 的联合类型?

在我的用例中,类型 Person 是自动生成的。我在对象上使用 Ramda 的 map 函数,将函数应用于对象的每个值:

import { map } from 'ramda'

classroom.people.forEach(person =>
  // Ramda’s `map` is applied to a `block` object here:
  map<Person, Person>(property => {
    // The type definitions for Ramda are not sufficiently strong to infer the type
    // of `property`, so it needs to be manually annotated.
    return someFunction(property)
  }, person)
)

我正在寻找的行为本质上等同于 keyof — 但据我所知,TypeScript 中没有 valueof。等效的实现会是什么样子?

非常感谢!


编辑: 通常,解决方案将按照@kaya3 的建议:type ValueOf<T> = T[keyof T]。但是,仔细检查后,我的情况似乎受到以下问题的困扰:

type PersonCommonFields = {
  age: number,
  name: string
}
type PersonFragment =
  | { favouriteColor: string }
  | { female: boolean }
  | { eyeColor: Color }
  | { born: Date }
type Person = PersonCommonFields & PersonFragment

在这种情况下,ValueOf<Person> 如上定义 returns number | string,即仅来自 PersonCommonFields 的值,忽略 PersonFragment。此示例的预期结果为 number | string | boolean | Color | Date.

是否有其他方法可以解决这种情况?

很多(很多!)提前致谢!

我注意到,如果您将 PersonFragment 中的 | 更改为 &,它会起作用(换句话说,创建单一类型而不是联合类型)。您似乎希望这些字段是可选的,您可以将 Partial 与单一类型一起使用吗(与将每个字段设为可选的行为相同)?

type PersonCommonFields = {
  age: number,
  name: string
}
type PersonFragment = Partial<{
  favouriteColor: string,
  female: boolean,
  eyeColor: Color,
  born: Date
}>
type Person = PersonCommonFields & PersonFragment;

type PersonTypes = Person[keyof Person]; // number | string | boolean | Color | Date

编辑:

@kaya3 在评论中指出,当前行为是至少应设置 PersonFragment 中的一个字段。如果这不是必需的,那么上面的方法应该有效。

假设要求实际上只存在 1 个字段,而不是更多?您可以使用自定义类型 XOR1 来强制执行此操作,生成的对象将允许您访问密钥。

// Note: Define these in your global typings file so they can be reused
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;

type PersonFragment = XOR<
  { favouriteColor: string },
  XOR<{ female: boolean }, XOR<{ eyeColor: Color }, { born: Date }>>
>;

1:

受@Wex 回答的启发,我找到了一个解决方法,虽然不完美但现在已经足够了。

graphql-codegen 生成以下类型:

type PersonCommonFields = {
  age: number,
  name: string
}
type A = { favoriteColor: string }
type B = { female: boolean }
type C = { eyeColor: Color }
type D = { born: Date }
type PersonFragment = A | B | C | D

由此我创建了 type Person = PersonCommonFields & PersonFragment,它在代码中使用。理想情况下,只有最后一种类型 (Person) 会被用来提出解决方案,因为随着它的发展和包含更多类型,它不需要与 PersonFragment 保持同步随着时间的推移,随着 GraphQL 模式的发展,它的联合(EFG 等)。

但是稍微重新排列一下,我们可以得到这个:

type PersonFields =
  | Person[keyof PersonCommonFields]
  | A[keyof A]
  | B[keyof B]
  | C[keyof C]
  | D[keyof D]

创建所需的类型 number | string | favoriteColor | boolean | Color | Date

ValueOf<T> = T[keyof T] 类型对联合类型不起作用,因为 keyof 不分布在联合上。例如,类型 keyof ({foo: 1} | {bar: 2})never 而不是 'foo' | 'bar'.

相反,我们可以使用 distributive conditional type,它 分配给联合(顾名思义)。

type ValueOfUnion<T> = T extends infer U ? U[keyof U] : never

type Test = ValueOfUnion<Person>
// Test = string | number | boolean | Color | Date

Playground Link