Kako kombinirati unije u TypeScriptu?
Imam
type a = { a: "hi", b: number }
type b = { a: "bye", b: number }
Želim nekako napraviti :
type c = a + b // there's nothing called "+"
i da se c zaključi kao
type c = { a: "hi" | "bye", b: number };
Pokušao sam napraviti:
import { UnionToIntersection } from 'utility-types'
type a = { a: "hi", b: number }
type b = { a: "bye", b: number }
type c = UnionToIntersection<a | b> // never
Ali c se tumači kao never.
uredi*
Ako ti ne smeta, evo još jedan slučaj upotrebe
type o = {
type: "bar";
foo: string;
bar: number;
} | {
type: "baz";
foo: string;
baz: boolean;
}
Želim na neki način pretvoriti ovu vrstu u sljedeću vrstu:
type: "bar" | "baz" ;
foo: string;
bar?: number;
baz?: boolean;
Odgovor #1
Ono što tražite vrlo je blisko operaciji identiteta; ako imate unijski tip poput A | B i pristupate njegovim svojstvima, svako od svojstava automatski će postati unija. Ako je to sve što ste željeli, možete jednostavno koristiti uniju takvu kakva jest, ili ako želite, napisati mapirani tip kako biste sveli uniju u jedan objektni tip.
Jedini problem ovdje je što želite da svojstva koja se pojavljuju samo u nekim članovima unije budu opcionalna u kombiniranom tipu, dok će unija obično jednostavno izostaviti takva svojstva. Dakle, prva stvar koju bismo trebali učiniti je pretvoriti svakog člana unije u novi tip s opcionalnim svojstvima tipa never za bilo koje svojstvo koje nije definirano u tom članu. To jest, želimo pretvoriti {a: 0, b: 1} | {b: 2, c: 3} u nešto poput {a: 0, b: 1, c?: never} | {a?: never, b: 2, c: 3}. To će se zatim kombinirati kako bi se formiralo {a?: 0, b: 1 | 2, c?: 3} kao što je željeno kada se spoje.
Spojiti to zajedno izgleda ovako:
type _Combine<T, K extends PropertyKey = T extends unknown ? keyof T : never> =
T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
type Combine<T> = { [K in keyof _Combine<T>]: _Combine<T>[K] }
Ovdje je _Combine<T> pomoćni tip koji koristi distributivne uvjetne tipove za razdvajanje T u članove unije i izvođenje operacija nad njima. Prvi je zadani tip argumenta za K koji prikuplja sve ključeve članova T (T extends unknown ? keyof T : never), a drugi je mjesto gdje presijecamo svakog člana T s objektom koji ima opcionalne ključeve tipa never za svaki ključ u K koji nije dio tog člana T.
I onda je Combine<T> tip mapiran identitetom preko _Combine<T> koji kolapsira ružne stvari poput ({a: 0, b: 1) & Partial<Record<"c", never>>) | ({b: 2, c: 3} & Partial<Record<"a", never>>) u željeni {a?: 0, b: 1 | 2, c?: 3}.
Isprobajmo to na tvom primjeru:
type O = {
type: "bar";
foo: string;
bar: number;
} | {
type: "baz";
foo: string;
baz: boolean;
}
type Z = Combine<O>;
/* type Z = {
type: "bar" | "baz";
foo: string;
bar?: number | undefined;
baz?: boolean | undefined;
} */
Izgleda dobro. Svojstva type i foo su obavezna, budući da postoje u svim članovima O, dok su svojstva bar i baz opcionalna, budući da nedostaju u barem jednom članu O.
Igralište poveznica na kod
Odgovor #2
Možeš samo koristiti
type c = a | b;
Budući da je b zajedničko svojstvo, ono će se spojiti bez promjena, a a će se spojiti kao "hi" | "bye".
Ovo bi neki IDE-ovi mogli prikazati kao { a: "hi", b: number } | { a: "bye", b: number } umjesto { a: "hi" | "bye", b: number }, ali je funkcionalno identično.
Pogledajte ovaj radni primjer: TS Playground
