Как сделать тип из значений типа объекта объединения / пересечения? - PullRequest
1 голос
/ 06 февраля 2020

У меня сложный тип, выясняющий, как определить тип как объединение всех возможных значений из предопределенного типа объекта.

Предположим, у нас есть автоматически сгенерированный тип Person, который выглядит следующим образом:

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

Как использовать тип Person для создания типа объединения, равного string | number | boolean?

В моем случае тип Person генерируется автоматически. Я использую функцию 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>, как определено выше, возвращает number | string, то есть только значения из PersonCommonFields, игнорируя PersonFragment. Ожидаемый результат для этого примера будет number | string | boolean | Color | Date.

Будет ли альтернативный подход к решению этой ситуации?

Большое (много!) Спасибо заранее!

Ответы [ 3 ]

1 голос
/ 06 февраля 2020

Тип ValueOf<T> = T[keyof T] не работает так, как вы хотите для типов объединений, потому что keyof не распространяется на объединения. Например, тип keyof ({foo: 1} | {bar: 2}) равен never вместо 'foo' | 'bar'.

Вместо этого мы можем использовать дистрибутивный условный тип , который распределяет по союзы (как следует из названия).

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

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

Playground Link

1 голос
/ 06 февраля 2020

Я заметил, что если вы измените | на & в 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 должно быть установлено. Если это не является обязательным требованием, вышеуказанное должно работать.

Допустим, на самом деле требуется только одно поле, а не больше? Вы можете использовать пользовательский тип XOR 1 для принудительного применения этого, и результирующий объект позволит вам получить доступ к ключам.

// 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 : { ссылка }

0 голосов
/ 06 февраля 2020

Вдохновленный ответом @ 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 по мере его развития и включения большего числа типов в свое объединение ( E, F, G и c.) С течением времени, поскольку схема GraphQL развивается.

Но переставив немного, мы можем добраться до этого:

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

, который создает желаемый тип number | string | favoriteColor | boolean | Color | Date.

...