Website-Suche

TypeScript Eigenschaften zu spezifischen Union-Fällen hinzufügen


Ich habe diesen Typ (vereinfacht):

type T1 = {
  kind: 'a';
  prop: string;
} | {
  kind: 'b';
  prop2: number;
};

Jetzt möchte ich eine Eigenschaft prop3: boolean aber nur für den Fall kind: 'a' hinzufügen, so wie ich geschrieben habe:

type T2 = {
  kind: 'a';
  prop: string;
  prop3: boolean;
} | {
  kind: 'b';
  prop2: number;
};

Ich habe Folgendes versucht:

type T2 = T1 & { prop3: boolean }; // Not works, adds to both `kind: 'a'` and `kind: 'b'`

type T3 = T1 & { kind: 'a'; prop3: boolean } // Adds to `kind: 'a'` properly, but intersects `kind: 'b' & 'a'` for `kind: 'b'`, and thus `kind: never`, and thus the whole case is `never`, so it omits `kind: 'b'` entirely - equals to `{ kind: 'a', prop: string, prop2: string }`

Ich vermute, die Antwort hat etwas mit den eingebauten Utility-Typen wie Exclude und Omit zu tun, kann aber keine passende Lösung finden.

Jede Hilfe wird geschätzt. Vielen Dank im Voraus.

Antwort #1

Ich denke, wenn du so etwas häufig machen wirst und einen Utility-Typ dafür brauchst, kannst du deinen eigenen erstellen:

type IntersectExtract<T, U, V> = T extends U ? T & V : T

Das bedeutet: schneide V mit genau den Mitgliedern der Vereinigung T, die U zuweisbar sind:

type T2 = IntersectExtract<T1, { kind: 'a' }, { prop3: boolean; }>

/* type T2 = {
    kind: 'b';
    prop2: number;
} | ({
    kind: 'a';
    prop: string;
} & {
    prop3: boolean;
}) */

declare const t2: T2;
if (t2.kind === "a") {
  t2.prop3 === true // okay
} else {
  t2.prop3 === true // error
  // ~~~~~ <-- prop3 does not exist on type {kind: "b", prop2: number}
}

Sieht aus wie das, was du willst.

Playground-Link zum Code

Antwort #2

Extrahieren und Ausschließen kann hier sehr hilfreich sein.

type T2 =
    Extract<T1, { kind: 'a' }> & { prop3: boolean } |
    Exclude<T1, { kind: 'a' }>;

Die erste Zeile extrahiert nur das Union-Mitglied, das { kind: 'a' } entspricht, und fügt die neue Eigenschaft in diesen Typ ein.

Die zweite Zeile ist der Typ aller anderen Mitglieder der Vereinigung, die nicht geändert werden. Also können wir einfach unseren modifizierten Typ nehmen und eine Vereinigung mit diesem Typ neu erstellen, um das gewünschte Ergebnis zu erhalten.

T2 wird jetzt gemeldet als:

{
    kind: 'b';
    prop2: number;
} | ({
    kind: 'a';
    prop: string;
} & {
    prop3: boolean;
})

Was sich vereinfacht zu:

{
    kind: 'b';
    prop2: number;
} | {
    kind: 'a';
    prop: string;
    prop3: boolean;
}

Spielplatz