Hvordan kombinere unioner i TypeScript?
Jeg har
type a = { a: "hi", b: number }
type b = { a: "bye", b: number }
Jeg vil på en eller annen måte gjøre:
type c = a + b // there's nothing called "+"
og få c utledet som
type c = { a: "hi" | "bye", b: number };
Jeg prøvde å gjøre:
import { UnionToIntersection } from 'utility-types'
type a = { a: "hi", b: number }
type b = { a: "bye", b: number }
type c = UnionToIntersection<a | b> // never
Men c blir tolket som never.
rediger*
Hvis du ikke har noe imot det, her er et annet bruksområde
type o = {
type: "bar";
foo: string;
bar: number;
} | {
type: "baz";
foo: string;
baz: boolean;
}
Jeg vil på en eller annen måte konvertere denne typen til følgende type:
type: "bar" | "baz" ;
foo: string;
bar?: number;
baz?: boolean;
Svar #1
Det du ber om, er veldig nær en identitetsoperasjon; hvis du har en unionstype som A | B og får tilgang til egenskapene dens, vil hver av egenskapene automatisk bli unions. Hvis det er alt du ønsket, kan du bare bruke unionen som den er, eller hvis du vil, skrive en kartlagt type for å slå sammen unionen til en enkelt objektstype.
Det eneste krappe punktet her er at du vil at egenskaper som bare vises i noen av medlemsmangfoldene, skal være valgfrie i den kombinerte typen, mens et mangfold vanligvis bare vil utelate slike egenskaper. Så det første vi bør gjøre er å konvertere hvert medlem av mangfoldet til en ny type med valgfrie egenskaper av typen never for alle egenskaper som ikke er definert i det medlemmet. Det vil si at vi ønsker å gjøre {a: 0, b: 1} | {b: 2, c: 3} om til noe som {a: 0, b: 1, c?: never} | {a?: never, b: 2, c: 3}. Dette vil da kombineres til å danne {a?: 0, b: 1 | 2, c?: 3} som ønsket når det slås sammen.
Å sette det sammen ser slik ut:
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] }
Her _Combine<T> er en hjelpetype som bruker distribuerbare betingede typer for å splitte T i union-medlemmer og utføre operasjoner på dem. Den første er standard typeargument for K som samler alle nøklene til medlemmene i T (T extends unknown ? keyof T : never), og den andre er der vi snitter hvert medlem av T med et objekt med valgfrie nøkler av typen never for hver nøkkel i K som ikke er en del av det medlemmet av T.
Og deretter er Combine<T> en identitetskartlagt type over _Combine<T> som kollapser stygge ting som ({a: 0, b: 1) & Partial<Record<"c", never>>) | ({b: 2, c: 3} & Partial<Record<"a", never>>) til det ønskede {a?: 0, b: 1 | 2, c?: 3}.
La oss teste det på eksemplet ditt:
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;
} */
Ser bra ut. type og foo egenskapene er påkrevde, siden de finnes i alle medlemmer av O, mens bar og baz egenskapene er valgfrie, siden de mangler i minst ett medlem av O.
Lenke til kode på lekeplassen
Svar #2
Du kan bare bruke
type c = a | b;
Fordi b er en vanlig egenskap, vil den bli slått sammen uten endringer, og a vil bli slått sammen som "hi" | "bye".
Dette kan vises av noen IDE-er som { a: "hi", b: number } | { a: "bye", b: number } i stedet for { a: "hi" | "bye", b: number }, men det er funksjonelt identisk.
Se dette fungerende eksemplet: TS Playground
