сопоставление тега с отмеченным членом объединения не работает для конкретного объединения, только для общего

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

Общая версия, указанная в ответе выше, работает:

type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = T extends Record<K, V> ? T : never

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

Вот мой код:

interface TypeA {
  tag: "a";
  data: string;
}

interface TypeB {
  tag: "b";
  data: [number];
}

interface TypeCD {
  tag: "c" | "d";
  data: number;
}

type Union = TypeA | TypeB | TypeCD;

type DiscriminatedUnion_0<V extends Union["tag"]> = Union extends Record<"tag", V> ? Union : never;
let shouldBeTypeA_0: DiscriminatedUnion_0<"a">; // doesn't work, type 'never'

// this works
type DiscriminatedUnion_1<V extends Union["tag"], T extends Union = Union> = T extends Record<"tag", V> ? T : never;
let shouldBeTypeA_1: DiscriminatedUnion_1<"a">;

type DiscriminatedUnion_2<V extends Union["tag"], T extends Union> = T extends Record<"tag", V> ? T : never;
let shouldBeTypeA_2: DiscriminatedUnion_2<"a", Union>;

person Ran Lottem    schedule 04.05.2019    source источник


Ответы (1)


В методе DiscriminateUnion используется распределительный условный тип, который работает только в том случае, если вы проверяете простой параметр универсального типа. Эта ссылка объясняет это довольно хорошо, или вы можете прочитать более подробное объяснение, если хотите. Единственный способ добиться такого поведения - выполнить условие для простого параметра универсального типа где-нибудь. Вот почему DiscriminatedUnion_1 сработало; вы проверили параметр типа T.

К счастью, вам не нужно играть в игры с параметрами типа по умолчанию, чтобы получить этот эффект. Проверка параметра универсального типа должна происходить, но она не должна быть непосредственно в вашем окончательном псевдониме типа.

Один из способов сделать это - использовать исходное определение DiscriminateUnion<T, K, V> и создать псевдоним, который его использует, например

type MyDiscriminateUnion<V extends Union["tag"]> = DiscriminateUnion<Union, "tag", V>;

Однако другой способ сделать это - использовать псевдоним предопределенного типа в стандартной библиотеке под названием Extract<T, U>, которая возвращает все составляющие объединенного типа T, соответствующие другому типу U. Определение следующее:

type Extract<T, U> = T extends U ? T : never;

При этом вы можете построить свой DiscriminatedUnion, не играя в игры с параметрами типа по умолчанию:

type DiscriminatedUnion<V extends Union["tag"]> = Extract<Union, Record<"tag", V>>;
let shouldBeTypeA: DiscriminatedUnion<"a">; // TypeA, hooray!

Хорошо, надеюсь, что это поможет; удачи!

person jcalz    schedule 05.05.2019