中文

探索 TypeScript 可辨識聯合,這是一個用於建立強大且類型安全的狀態機的強大工具。 學習如何定義狀態、處理轉換,並利用 TypeScript 的類型系統來提高程式碼的可靠性。

TypeScript 可辨識聯合:建立類型安全的狀態機

在軟體開發領域,有效地管理應用程式狀態至關重要。狀態機為建模複雜的有狀態系統提供了一個強大的抽象,確保可預測的行為並簡化關於系統邏輯的推理。TypeScript 憑藉其強大的類型系統,提供了一種極好的機制,可以使用可辨識聯合(也稱為標記聯合或代數資料類型)來建立類型安全的狀態機。

什麼是可辨識聯合?

可辨識聯合是一種表示可以有多種不同類型的值的類型。這些類型中的每一種(稱為聯合的成員)都共享一個公共的、不同的屬性,稱為辨識符或標記。此辨識符允許 TypeScript 精確地確定當前哪個聯合成員處於活動狀態,從而實現強大的類型檢查和自動完成。

可以把它想像成交通號誌。它可以處於三種狀態之一:紅色、黃色或綠色。`color` 屬性充當辨識符,告訴我們燈光處於哪種狀態。

為什麼使用可辨識聯合來構建狀態機?

在使用 TypeScript 構建狀態機時,可辨識聯合帶來了幾個主要優點:

使用可辨識聯合定義狀態機

讓我們用一個實際的例子來說明如何使用可辨識聯合來定義狀態機:訂單處理系統。訂單可以處於以下狀態:待處理處理中已出貨已送達

步驟 1:定義狀態類型

首先,我們為每個狀態定義單獨的類型。每種類型都將具有一個充當辨識符的 `type` 屬性,以及任何特定於狀態的資料。


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

步驟 2:建立可辨識聯合類型

接下來,我們使用 `|`(聯合)運算符組合這些單獨的類型來建立可辨識聯合。


type OrderState = Pending | Processing | Shipped | Delivered;

現在,`OrderState` 表示一個可以是 `Pending`、`Processing`、`Shipped` 或 `Delivered` 的值。每個狀態中的 `type` 屬性充當辨識符,允許 TypeScript 區分它們。

處理狀態轉換

現在我們已經定義了狀態機,我們需要一種在狀態之間轉換的機制。讓我們建立一個 `processOrder` 函數,該函數將當前狀態和一個操作作為輸入,並傳回新狀態。


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; // 沒有狀態變更

    case "processing":
      if (action.type === "shipOrder") {
        return {
          type: "shipped",
          orderId: state.orderId,
          trackingNumber: action.payload.trackingNumber,
        };
      }
      return state; // 沒有狀態變更

    case "shipped":
      if (action.type === "deliverOrder") {
        return {
          type: "delivered",
          orderId: state.orderId,
          deliveryDate: new Date(),
        };
      }
      return state; // 沒有狀態變更

    case "delivered":
      // 訂單已送達,沒有其他操作
      return state;

    default:
      // 由於窮盡性檢查,這不應該發生
      return state; // 或拋出錯誤
  }
}

說明

利用窮盡性檢查

TypeScript 的窮盡性檢查是一項強大的功能,可確保您處理狀態機中的所有可能狀態。如果您向 `OrderState` 聯合新增一個新狀態,但忘記更新 `processOrder` 函數,TypeScript 將標記一個錯誤。

若要啟用窮盡性檢查,可以使用 `never` 類型。在 switch 陳述式的 `default` 案例內,將狀態指派給 `never` 類型的變數。


function processOrder(state: OrderState, action: Action): OrderState {
  switch (state.type) {
    // ... (先前的案例) ...

    default:
      const _exhaustiveCheck: never = state;
      return _exhaustiveCheck; // 或拋出錯誤
  }
}

如果 `switch` 陳述式處理所有可能的 `OrderState` 值,則 `_exhaustiveCheck` 變數將為 `never` 類型,並且程式碼將會編譯。但是,如果您向 `OrderState` 聯合新增一個新狀態,但忘記在 `switch` 陳述式中處理它,則 `_exhaustiveCheck` 變數將為不同的類型,並且 TypeScript 將拋出編譯時錯誤,從而提醒您缺少案例。

實際範例和應用

可辨識聯合適用於簡單訂單處理系統以外的各種情況:

範例:UI 狀態管理

讓我們考慮一個管理從 API 提取資料的 UI 元件狀態的簡單範例。我們可以定義以下狀態:


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 

按一下按鈕以載入資料。

; case "loading": return

載入中...

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

錯誤:{state.message}

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

此範例示範了如何使用可辨識聯合來有效地管理 UI 元件的不同狀態,確保根據當前狀態正確呈現 UI。`renderUI` 函數適當地處理每個狀態,從而提供了一種清晰且類型安全的方式來管理 UI。

使用可辨識聯合的最佳實務

若要在您的 TypeScript 專案中有效地利用可辨識聯合,請考慮以下最佳實務:

進階技術

條件類型

條件類型可以與可辨識聯合組合使用,以建立更強大、更靈活的狀態機。例如,您可以使用條件類型來根據當前狀態為函數定義不同的傳回類型。


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

此函數使用一個簡單的 `if` 陳述式,但可以使用條件類型使其更健壯,以確保始終傳回特定的類型。

公用程式類型

TypeScript 的公用程式類型(例如 `Extract` 和 `Omit`)在處理可辨識聯合時非常有用。`Extract` 允許您根據條件從聯合類型中提取特定的成員,而 `Omit` 允許您從類型中刪除屬性。


// 從 UIState 聯合中提取「成功」狀態
type SuccessState = Extract, { type: "success" }>;

// 從 Error 介面中省略 'message' 屬性
type ErrorWithoutMessage = Omit;

各個產業的真實世界範例

可辨識聯合的強大功能擴展到各個產業和應用領域:

結論

TypeScript 可辨識聯合提供了一種強大且類型安全的方式來建立狀態機。透過清楚地定義可能的狀態和轉換,您可以建立更強大、更易於維護和理解的程式碼。類型安全、窮盡性檢查和增強的程式碼完成的組合使可辨識聯合成為處理複雜狀態管理的任何 TypeScript 開發人員的寶貴工具。在您的下一個專案中採用可辨識聯合,並親身體驗類型安全狀態管理的好處。正如我們透過從電子商務到醫療保健,以及從物流到教育的各種範例所展示的那樣,透過可辨識聯合實現類型安全狀態管理的原則具有普遍的適用性。

無論您是在建立一個簡單的 UI 元件還是複雜的企業應用程式,可辨識聯合都可以幫助您更有效地管理狀態並降低執行階段錯誤的風險。因此,請深入研究並使用 TypeScript 探索類型安全狀態機的世界!