Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Discriminated Unions - Warianty Komponentow

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.

Problem - różne ksztalty danych

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}

Rozwiązanie - discriminated unions

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;

Zawezanie typow (type narrowing)

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}

Warianty komponentów UI

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}

Exhaustive checking - nigdy nie przegap wariantu

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}
Vai a CodeWorlds