TypeScript: универсальный интерфейс как объединение других интерфейсов - PullRequest
1 голос
/ 24 мая 2019

Я хотел бы создать общий интерфейс со свойствами, представляющими объединение свойств из других интерфейсов.

Допустим, у меня есть два интерфейса

interface A {
    something: string;
    somethingElse: number;
}

interface B {
    something: Array<string>;
}

Я не хочу писатьinterface C как

interface C {
    something: string | Array<string>;
    somethingElse?: number;
}

, потому что это будет означать, что всякий раз, когда я изменяю любой из интерфейсов A или B, мне нужно будет также вручную изменить интерфейс C.

Из того, что я видел в документации TypeScript, а также из ответов здесь о переполнении стека, я должен объявить новый тип

type unionOfKeys = keyof A | keyof B;

и реализовать универсальную форму интерфейса

interface GenericInterface {
    <T>(arg: T): T;
}

Я думал в направлении

interface C {
    <T extends unionOfKeys>(arg: T): T extends unionOfKeys ? A[T] | B[T] : any
}

, но это не помогло из-за несоответствия между рядом свойств и их типов.

Я был бы признателен за любую помощь.Спасибо.

Ответы [ 2 ]

2 голосов
/ 24 мая 2019

Я думаю, что следующая версия MergeUnion<T> может вести себя так, как вы хотите:

type MergeUnion<T> = (
  keyof T extends infer K ? [K] extends [keyof T] ? Pick<T, K> & {
    [P in Exclude<(T extends any ? keyof T : never), K>]?:
    T extends Partial<Record<P, infer V>> ? V : never
  } : never : never
) extends infer U ? { [K in keyof U]: U[K] } : never;

type C = MergeUnion<A | B>;
// type C = { 
//  something: string | string[]; 
//  somethingElse?: number | undefined; }
// }

Это похоже на другой ответ в том, что он находит объединение всех ключей всех составляющих T (назовите это UnionKeys, определенное как T extends any ? keyof T : never) и возвращает сопоставленный тип со всеми ними.Разница в том, что здесь мы также находим пересечение всех ключей всех составляющих T (назовите его IntersectKeys, определяемое как просто keyof T) и разделяем ключи T на два набора ключей.Один из пересечения присутствует в каждой составляющей, поэтому мы можем просто сделать Pick<T, IntesectKeys>, чтобы получить общие свойства.Остаток Exclude<UnionKeys, IntersectKeys> будет необязательным в окончательном типе.

Это довольно уродливо, и я бы убрался, если бы почувствовал себя лучше.Проблема заключается в том, что все еще существует проблема, когда любое из свойств, отображаемых во всех компонентах, является необязательным.Существует ошибка в TypeScript (по состоянию на TS3.5), где в {a?: string} | {a?: number} свойство a рассматривается как обязательное свойство, такое как {a: string | number | undefined}, тогда как оно будетправильнее рассматривать его как необязательный, если какой-либо из компонентов имеет его как необязательный.Эта ошибка доходит до MergeUnion:

type Oops = MergeUnion<{a?: string} | {a?: number}>
// type Oops =  { a: string | number | undefined; }

У меня нет отличного ответа, который не является даже более сложным, поэтому я остановлюсь здесь.Может быть, этого достаточно для ваших нужд.Или, может быть, @ TitianCernicova-Dragomir ответ достаточно для ваших нужд.Надеюсь, что эти ответы помогут вам;удачи!

Ссылка на код

1 голос
/ 24 мая 2019

Ни типы пересечений, ни типы соединений не приведут нас к C.Тип объединения (A | B разрешает доступ только к общим свойствам).Пересечение (A & B) позволит получить доступ ко всем свойствам, но если свойства не совпадают между A и B, свойство будет пересечением двух свойств (например, something будет string & Array<string>;, что не оченьполезно здесь).

Решение состоит в том, чтобы создать собственный сопоставленный тип, который будет принимать ключи от всех переданных типов, и создавать объединение типов свойств из каждого члена:

interface A {
    something: string;
    somethingElse: number;
}

interface B {
    something: Array<string>;
}

type KeyOf<T> = T extends any ? keyof T : never;
type PropValue<T, K extends PropertyKey> = T extends Record<K, infer V> ? V : never;
type Merge<T> = {
    [P in KeyOf<T>] : PropValue<T, P>
}

type C = Merge<A | B>
// type C = {
//     something: string | string[];
//     somethingElse: number;
// }

KeyOf примет T и, если T является объединением, он вернет ключи всех членов объединения.Это делается с использованием дистрибутивного свойства условных типов

type K = KeyOf<{a : number} | { b: number }> //  "a" | "b". 

Это необходимо, так как keyof для объединения будет возвращать только общие члены.(keyof ({a : number} | { b: number }) is never).

PropValue также использует свойство распределения условных типов для извлечения объединения всех типов значений для ключа.

type V = PropValue<{a : number} | {a : string} |{ b: number }, 'a'> //  string | number

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...