Norsk

Utforsk TypeScript diskriminerte unioner, et kraftig verktøy for å bygge robuste og typesikre statmaskiner. Lær hvordan du definerer tilstander, håndterer overganger og utnytter TypeScripts typesystem for økt kodepålitelighet.

TypeScript Diskriminerte Unioner: Bygge Typer-Sikre Statmaskiner

I programvareutvikling er effektiv håndtering av applikasjonstilstand avgjørende. Statmaskiner gir en kraftig abstraksjon for modellering av komplekse tilstandssystemer, og sikrer forutsigbar oppførsel og forenkler resonnementet om systemets logikk. TypeScript, med sitt robuste typesystem, tilbyr en fantastisk mekanisme for å bygge typesikre statmaskiner ved hjelp av diskriminerte unioner (også kjent som taggede unioner eller algebraiske datatyper).

Hva er diskriminerte unioner?

En diskriminert union er en type som representerer en verdi som kan være en av flere forskjellige typer. Hver av disse typene, kjent som medlemmer av unionen, deler en felles, distinkt egenskap kalt diskriminanten eller taggen. Denne diskriminanten lar TypeScript nøyaktig bestemme hvilket medlem av unionen som er aktivt for øyeblikket, noe som muliggjør kraftig typekontroll og autokomplettering.

Tenk på det som et trafikklys. Det kan være i en av tre tilstander: Rød, Gul eller Grønn. Egenskapen 'farge' fungerer som diskriminanten, og forteller oss nøyaktig hvilken tilstand lyset er i.

Hvorfor bruke diskriminerte unioner for statmaskiner?

Diskriminerte unioner gir flere viktige fordeler når du bygger statmaskiner i TypeScript:

Definere en statmaskin med diskriminerte unioner

La oss illustrere hvordan du definerer en statmaskin ved hjelp av diskriminerte unioner med et praktisk eksempel: et ordrebehandlingssystem. En ordre kan være i følgende tilstander: Venter, Behandler, Sendt og Levert.

Trinn 1: Definer tilstandstypene

Først definerer vi de individuelle typene for hver tilstand. Hver type vil ha en `type`-egenskap som fungerer som diskriminanten, sammen med eventuelle tilstandsspesifikke data.


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;
}

Trinn 2: Opprett den diskriminerte unionstypen

Deretter oppretter vi den diskriminerte unionen ved å kombinere disse individuelle typene ved hjelp av `|`-operatoren (union).


type OrderState = Pending | Processing | Shipped | Delivered;

Nå representerer `OrderState` en verdi som kan være enten `Pending`, `Processing`, `Shipped` eller `Delivered`. `type`-egenskapen i hver tilstand fungerer som diskriminanten, og lar TypeScript skille mellom dem.

Håndtering av tilstandsoverganger

Nå som vi har definert statmaskinen vår, trenger vi en mekanisme for å gå over mellom tilstander. La oss lage en `processOrder`-funksjon som tar gjeldende tilstand og en handling som input, og returnerer den nye tilstanden.


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; // Ingen tilstandsendring

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

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

    case "delivered":
      // Ordren er allerede levert, ingen flere handlinger
      return state;

    default:
      // Dette skal aldri skje på grunn av uttømmende kontroll
      return state; // Eller kast en feil
  }
}

Forklaring

Utnytte Uttømmende Kontroll

TypeScripts uttømmende kontroll er en kraftig funksjon som sikrer at du håndterer alle mulige tilstander i statmaskinen din. Hvis du legger til en ny tilstand i `OrderState`-unionen, men glemmer å oppdatere `processOrder`-funksjonen, vil TypeScript flagge en feil.

For å aktivere uttømmende kontroll, kan du bruke `never`-typen. Inne i `default`-casen i din switch-setning, tildel tilstanden til en variabel av typen `never`.


function processOrder(state: OrderState, action: Action): OrderState {
  switch (state.type) {
    // ... (forrige caser) ...

    default:
      const _exhaustiveCheck: never = state;
      return _exhaustiveCheck; // Eller kast en feil
  }
}

Hvis switch-setningen håndterer alle mulige `OrderState`-verdier, vil `_exhaustiveCheck`-variabelen være av typen `never`, og koden vil kompileres. Men hvis du legger til en ny tilstand i `OrderState`-unionen og glemmer å håndtere den i switch-setningen, vil `_exhaustiveCheck`-variabelen være av en annen type, og TypeScript vil kaste en kompileringsfeil, og varsle deg om den manglende casen.

Praktiske eksempler og applikasjoner

Diskriminerte unioner er anvendbare i et bredt spekter av scenarier utover enkle ordrebehandlingssystemer:

Eksempel: UI-tilstandshåndtering

La oss vurdere et enkelt eksempel på å håndtere tilstanden til en UI-komponent som henter data fra et API. Vi kan definere følgende tilstander:


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 

Klikk på knappen for å laste data.

; case "loading": return

Laster...

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

Feil: {state.message}

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

Dette eksemplet demonstrerer hvordan diskriminerte unioner kan brukes til effektivt å håndtere de forskjellige tilstandene til en UI-komponent, og sikre at UI-en gjengis riktig basert på gjeldende tilstand. `renderUI`-funksjonen håndterer hver tilstand på riktig måte, og gir en klar og typesikker måte å administrere UI-en på.

Beste praksiser for bruk av diskriminerte unioner

For å effektivt bruke diskriminerte unioner i TypeScript-prosjektene dine, bør du vurdere følgende beste praksiser:

Avanserte teknikker

Betingede typer

Betingede typer kan kombineres med diskriminerte unioner for å lage enda kraftigere og mer fleksible statmaskiner. For eksempel kan du bruke betingede typer til å definere forskjellige returtyper for en funksjon basert på gjeldende tilstand.


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

Denne funksjonen bruker en enkel `if`-setning, men kan gjøres mer robust ved hjelp av betingede typer for å sikre at en spesifikk type alltid returneres.

Verktøytyper

TypeScripts verktøytyper, for eksempel `Extract` og `Omit`, kan være nyttige når du arbeider med diskriminerte unioner. `Extract` lar deg trekke ut bestemte medlemmer fra en unionstype basert på en betingelse, mens `Omit` lar deg fjerne egenskaper fra en type.


// Trekk ut "success"-tilstanden fra UIState-unionen
type SuccessState = Extract, { type: "success" }>;

// Utelat 'message'-egenskapen fra Error-grensesnittet
type ErrorWithoutMessage = Omit;

Reelle eksempler på tvers av forskjellige bransjer

Kraften til diskriminerte unioner strekker seg på tvers av forskjellige bransjer og applikasjonsdomener:

Konklusjon

TypeScript diskriminerte unioner gir en kraftig og typesikker måte å bygge statmaskiner på. Ved å tydelig definere de mulige tilstandene og overgangene, kan du lage mer robust, vedlikeholdbar og forståelig kode. Kombinasjonen av typesikkerhet, uttømmende kontroll og forbedret kodekomplettering gjør diskriminerte unioner til et uvurderlig verktøy for enhver TypeScript-utvikler som arbeider med kompleks statushåndtering. Omfavn diskriminerte unioner i ditt neste prosjekt og opplev fordelene med typesikker statushåndtering på egenhånd. Som vi har vist med ulike eksempler fra e-handel til helsevesen og logistikk til utdanning, er prinsippet om typesikker statushåndtering gjennom diskriminerte unioner universelt anvendelig.

Enten du bygger en enkel UI-komponent eller en kompleks bedriftsapplikasjon, kan diskriminerte unioner hjelpe deg med å administrere tilstanden mer effektivt og redusere risikoen for runtime-feil. Så dykk inn og utforsk verden av typesikre statmaskiner med TypeScript!