Polski

Odkryj unie rozłączne w TypeScript – potężne narzędzie do budowania solidnych maszyn stanów z bezpieczeństwem typów. Dowiedz się jak definiować stany, obsługiwać przejścia i zwiększać niezawodność kodu.

Unie rozłączne w TypeScript: Budowanie maszyn stanów z bezpieczeństwem typów

W świecie tworzenia oprogramowania, efektywne zarządzanie stanem aplikacji jest kluczowe. Maszyny stanów dostarczają potężnej abstrakcji do modelowania złożonych systemów stanowych, zapewniając przewidywalne zachowanie i upraszczając rozumowanie o logice systemu. TypeScript, ze swoim solidnym systemem typów, oferuje fantastyczny mechanizm do budowania bezpiecznych typowo maszyn stanów przy użyciu unii rozłącznych (znanych również jako unie tagowane lub algebraiczne typy danych).

Czym są unie rozłączne?

Unia rozłączna to typ, który reprezentuje wartość mogącą być jednym z kilku różnych typów. Każdy z tych typów, znany jako członek unii, dzieli wspólną, wyróżniającą właściwość zwaną dyskryminatorem lub tagiem. Ten dyskryminator pozwala TypeScriptowi precyzyjnie określić, który członek unii jest aktualnie aktywny, umożliwiając potężne sprawdzanie typów i autouzupełnianie.

Pomyśl o tym jak o sygnalizacji świetlnej. Może być w jednym z trzech stanów: Czerwony, Żółty lub Zielony. Właściwość 'kolor' działa jak dyskryminator, mówiąc nam dokładnie, w jakim stanie jest światło.

Dlaczego używać unii rozłącznych do maszyn stanów?

Unie rozłączne przynoszą kilka kluczowych korzyści podczas budowania maszyn stanów w TypeScript:

Definiowanie maszyny stanów za pomocą unii rozłącznych

Zilustrujmy, jak zdefiniować maszynę stanów za pomocą unii rozłącznych na praktycznym przykładzie: systemie przetwarzania zamówień. Zamówienie może być w następujących stanach: Oczekujące, W trakcie realizacji, Wysłane i Dostarczone.

Krok 1: Zdefiniuj typy stanów

Najpierw definiujemy poszczególne typy dla każdego stanu. Każdy typ będzie miał właściwość `type` działającą jako dyskryminator, wraz z wszelkimi danymi specyficznymi dla danego stanu.


interface Pending {
  type: "pending";
  orderId: string;
  customerName: string;
  items: string[];
}

interface Processing {
  type: "processing";
  orderId: string;
  assignedAgent: string;
}

interface Shipped {
  type: "shipped";
  orderId: string;
  trackingNumber: string;
}

interface Delivered {
  type: "delivered";
  orderId: string;
  deliveryDate: Date;
}

Krok 2: Stwórz typ unii rozłącznej

Następnie tworzymy unię rozłączną, łącząc te poszczególne typy za pomocą operatora `|` (unia).


type OrderState = Pending | Processing | Shipped | Delivered;

Teraz `OrderState` reprezentuje wartość, która może być `Pending`, `Processing`, `Shipped` lub `Delivered`. Właściwość `type` w każdym stanie działa jako dyskryminator, pozwalając TypeScriptowi na ich rozróżnienie.

Obsługa przejść między stanami

Gdy już zdefiniowaliśmy naszą maszynę stanów, potrzebujemy mechanizmu do przechodzenia między stanami. Stwórzmy funkcję `processOrder`, która przyjmuje bieżący stan i akcję jako dane wejściowe i zwraca nowy stan.


interface Action {
  type: string;
  payload?: any;
}

function processOrder(state: OrderState, action: Action): OrderState {
  switch (state.type) {
    case "pending":
      if (action.type === "startProcessing") {
        return {
          type: "processing",
          orderId: state.orderId,
          assignedAgent: action.payload.agentId,
        };
      }
      return state; // Brak zmiany stanu

    case "processing":
      if (action.type === "shipOrder") {
        return {
          type: "shipped",
          orderId: state.orderId,
          trackingNumber: action.payload.trackingNumber,
        };
      }
      return state; // Brak zmiany stanu

    case "shipped":
      if (action.type === "deliverOrder") {
        return {
          type: "delivered",
          orderId: state.orderId,
          deliveryDate: new Date(),
        };
      }
      return state; // Brak zmiany stanu

    case "delivered":
      // Zamówienie jest już dostarczone, brak dalszych akcji
      return state;

    default:
      // To nigdy nie powinno się zdarzyć dzięki sprawdzaniu kompletności
      return state; // Lub rzuć błąd
  }
}

Wyjaśnienie

Wykorzystanie sprawdzania kompletności

Sprawdzanie kompletności (exhaustiveness checking) w TypeScript to potężna funkcja, która zapewnia obsługę wszystkich możliwych stanów w maszynie stanów. Jeśli dodasz nowy stan do unii `OrderState`, ale zapomnisz zaktualizować funkcję `processOrder`, TypeScript zgłosi błąd.

Aby włączyć sprawdzanie kompletności, możesz użyć typu `never`. Wewnątrz przypadku `default` instrukcji switch, przypisz stan do zmiennej typu `never`.


function processOrder(state: OrderState, action: Action): OrderState {
  switch (state.type) {
    // ... (poprzednie przypadki) ...

    default:
      const _exhaustiveCheck: never = state;
      return _exhaustiveCheck; // Lub rzuć błąd
  }
}

Jeśli instrukcja `switch` obsługuje wszystkie możliwe wartości `OrderState`, zmienna `_exhaustiveCheck` będzie typu `never`, a kod się skompiluje. Jednakże, jeśli dodasz nowy stan do unii `OrderState` i zapomnisz go obsłużyć w instrukcji `switch`, zmienna `_exhaustiveCheck` będzie innego typu, a TypeScript zgłosi błąd w czasie kompilacji, informując Cię o brakującym przypadku.

Praktyczne przykłady i zastosowania

Unie rozłączne mają zastosowanie w szerokim zakresie scenariuszy, wykraczających poza proste systemy przetwarzania zamówień:

Przykład: Zarządzanie stanem UI

Rozważmy prosty przykład zarządzania stanem komponentu UI, który pobiera dane z API. Możemy zdefiniować następujące stany:


interface Initial {
  type: "initial";
}

interface Loading {
  type: "loading";
}

interface Success {
  type: "success";
  data: T;
}

interface Error {
  type: "error";
  message: string;
}

type UIState = Initial | Loading | Success | Error;

function renderUI(state: UIState): React.ReactNode {
  switch (state.type) {
    case "initial":
      return 

Kliknij przycisk, aby załadować dane.

; case "loading": return

Ładowanie...

; case "success": return
{JSON.stringify(state.data, null, 2)}
; case "error": return

Błąd: {state.message}

; default: const _exhaustiveCheck: never = state; return _exhaustiveCheck; } }

Ten przykład demonstruje, jak unie rozłączne mogą być efektywnie używane do zarządzania różnymi stanami komponentu UI, zapewniając, że interfejs użytkownika jest renderowany poprawnie w zależności od bieżącego stanu. Funkcja `renderUI` odpowiednio obsługuje każdy stan, zapewniając jasny i bezpieczny typowo sposób zarządzania UI.

Dobre praktyki używania unii rozłącznych

Aby efektywnie wykorzystywać unie rozłączne w swoich projektach TypeScript, rozważ następujące dobre praktyki:

Zaawansowane techniki

Typy warunkowe

Typy warunkowe można łączyć z uniami rozłącznymi, aby tworzyć jeszcze potężniejsze i bardziej elastyczne maszyny stanów. Na przykład, możesz użyć typów warunkowych do definiowania różnych typów zwracanych przez funkcję w zależności od bieżącego stanu.


function getData(state: UIState): T | undefined {
  if (state.type === "success") {
    return state.data;
  }
  return undefined;
}

Ta funkcja używa prostej instrukcji `if`, ale mogłaby być bardziej solidna dzięki użyciu typów warunkowych, aby zapewnić, że zawsze zwracany jest określony typ.

Typy pomocnicze (Utility Types)

Typy pomocnicze TypeScript, takie jak `Extract` i `Omit`, mogą być pomocne podczas pracy z uniami rozłącznymi. `Extract` pozwala wyodrębnić określonych członków z typu unii na podstawie warunku, podczas gdy `Omit` pozwala usunąć właściwości z typu.


// Wyodrębnij stan "success" z unii UIState
type SuccessState = Extract, { type: "success" }>;

// Pomiń właściwość 'message' z interfejsu Error
type ErrorWithoutMessage = Omit;

Przykłady z życia wzięte z różnych branż

Moc unii rozłącznych rozciąga się na różne branże i domeny aplikacji:

Podsumowanie

Unie rozłączne w TypeScript zapewniają potężny i bezpieczny typowo sposób na budowanie maszyn stanów. Dzięki jasnemu zdefiniowaniu możliwych stanów i przejść można tworzyć bardziej solidny, łatwiejszy w utrzymaniu i zrozumiały kod. Połączenie bezpieczeństwa typów, sprawdzania kompletności i ulepszonego autouzupełniania kodu czyni unie rozłączne nieocenionym narzędziem dla każdego programisty TypeScript zajmującego się złożonym zarządzaniem stanem. Wykorzystaj unie rozłączne w swoim następnym projekcie i doświadcz korzyści płynących z bezpiecznego typowo zarządzania stanem na własnej skórze. Jak pokazaliśmy na różnorodnych przykładach od e-commerce po opiekę zdrowotną i od logistyki po edukację, zasada bezpiecznego typowo zarządzania stanem za pomocą unii rozłącznych jest uniwersalnie stosowalna.

Niezależnie od tego, czy budujesz prosty komponent UI, czy złożoną aplikację korporacyjną, unie rozłączne mogą pomóc Ci efektywniej zarządzać stanem i zmniejszyć ryzyko błędów wykonania. Zanurz się więc i odkryj świat bezpiecznych typowo maszyn stanów z TypeScript!