W kosmosie różne obiekty wymagaja różnego traktowania - planeta, gwiazda i asteroida to różne byty z różnymi wlasciwosciami. Discriminated unions pozwalają TypeScript rozroznic warianty obiektu na podstawie jednego pola.
Wyobraź sobie komponent, który wyświetla różne typy obiektów kosmicznych. Każdy typ ma inne pola:
1// ZLE - nie wiadomo które pola sa dostępne
2interface SpaceObject {
3 type: string;
4 name: string;
5 radius?: number;
6 temperature?: number;
7 composition?: string[];
8 orbitalPeriod?: number;
9}
10
11function SpaceInfo({ obj }: { obj: SpaceObject }) {
12 // Musisz sprawdzać każde pole z osobna
13 if (obj.radius) { /* ... */ }
14 if (obj.temperature) { /* ... */ }
15 // Latwo o błędy!
16}Zamiast jednego interfejsu z opcjonalnymi polami, tworzymy osobne interfejsy połączone wspólnym polem (dyskryminatorem):
1// Kazdy typ ma wspólne pole 'kind' o roznej wartości
2interface PlanetData {
3 kind: "planet";
4 name: string;
5 radius: number;
6 habitable: boolean;
7 moons: number;
8}
9
10interface StarData {
11 kind: "star";
12 name: string;
13 temperature: number;
14 luminosity: number;
15 spectralClass: string;
16}
17
18interface AsteroidData {
19 kind: "asteroid";
20 name: string;
21 composition: string[];
22 diameter: number;
23}
24
25// Union type - obiekt może byc jednym z trzech typow
26type SpaceObject = PlanetData | StarData | AsteroidData;TypeScript automatycznie zaweza typ na podstawie pola dyskryminatora:
1function SpaceInfo({ obj }: { obj: SpaceObject }) {
2 // Wspolne pole - dostępne dla wszystkich wariantow
3 const header = <h2>{obj.name}</h2>;
4
5 // Switch na dyskryminator
6 switch (obj.kind) {
7 case "planet":
8 // TypeScript WIE że obj to PlanetData
9 return (
10 <div>
11 {header}
12 <p>Promien: {obj.radius} km</p>
13 <p>Ksiezyce: {obj.moons}</p>
14 <p>{obj.habitable ? "Zamieszkiwalna" : "Niezamieszkiwalna"}</p>
15 </div>
16 );
17 case "star":
18 // TypeScript WIE że obj to StarData
19 return (
20 <div>
21 {header}
22 <p>Temperatura: {obj.temperature}K</p>
23 <p>Klasa: {obj.spectralClass}</p>
24 </div>
25 );
26 case "asteroid":
27 // TypeScript WIE że obj to AsteroidData
28 return (
29 <div>
30 {header}
31 <p>Srednica: {obj.diameter} km</p>
32 <p>Sklad: {obj.composition.join(", ")}</p>
33 </div>
34 );
35 }
36}Discriminated unions sa idealne do tworzenia wariantow komponentów UI:
1// Warianty alertow
2type AlertProps =
3 | { variant: "success"; message: string }
4 | { variant: "error"; message: string; retryAction: () => void }
5 | { variant: "warning"; message: string; dismissable: boolean }
6 | { variant: "info"; message: string; link?: string };
7
8function Alert(props: AlertProps) {
9 const baseClass = "alert";
10
11 switch (props.variant) {
12 case "success":
13 return <div className={`${baseClass} success`}>{props.message}</div>;
14 case "error":
15 return (
16 <div className={`${baseClass} error`}>
17 {props.message}
18 <button onClick={props.retryAction}>Ponow</button>
19 </div>
20 );
21 case "warning":
22 return (
23 <div className={`${baseClass} warning`}>
24 {props.message}
25 {props.dismissable && <button>Zamknij</button>}
26 </div>
27 );
28 case "info":
29 return (
30 <div className={`${baseClass} info`}>
31 {props.message}
32 {props.link && <a href={props.link}>Wiecej</a>}
33 </div>
34 );
35 }
36}1// Funkcja pomocnicza - TypeScript zglosi błąd jeśli nie obsluzysz wszystkich wariantow
2function assertNever(value: never): never {
3 throw new Error(`Nieobsluzony wariant: ${value}`);
4}
5
6function getLabel(obj: SpaceObject): string {
7 switch (obj.kind) {
8 case "planet": return "Planeta";
9 case "star": return "Gwiazda";
10 case "asteroid": return "Asteroida";
11 default: return assertNever(obj); // blad jesli dodasz nowy typ i nie obsluzysz go
12 }
13}