ts中如何限制方法傳入的引數data必須是陣列,且為偶數項?

龙陌發表於2024-06-25

在 TypeScript 中,要限制一個方法的引數 data 必須是陣列且陣列的長度為偶數,可以透過定義一個型別守衛來實現。這裡有一個示例:

function isEvenArray<T>(arg: T[]): arg is T[] {
  return Array.isArray(arg) && arg.length % 2 === 0;
}

function processEvenArray(data: any): void {
  if (!isEvenArray(data)) {
    throw new Error('data must be an array with an even number of elements');
  }
  // 此時 data 型別已被約束為偶數長度的陣列,可以安全使用
  console.log(data);
}

// 測試
try {
  processEvenArray([1, 2, 3, 4]); // 正確,偶數個元素
  processEvenArray([1, 2, 3]);    // 錯誤,將丟擲異常
} catch (error) {
  console.error(error.message);
}

在這個例子中,isEvenArray 函式充當型別守衛,它檢查傳入的引數是否為陣列並且長度是偶數。
processEvenArray 函式中,我們使用這個型別守衛來驗證 data 引數,如果不滿足條件就丟擲錯誤。
這樣,只有當 data 是一個偶數長度的陣列時,才會執行函式體內的程式碼。

可以直接定義一個自定義型別來約束引數必須是偶數長度的陣列。
這樣,在傳遞引數的時候,TypeScript 編譯器就會靜態檢查引數是否符合這個約束條件,而不需要在執行時透過型別守衛來檢查。
以下是定義這樣一個型別的方法:

type EvenArray<T> = {
  [index in number]: T;
  length: number;
} & { 
  readonly _customBrand: unique symbol; // 使用唯一符號作為品牌標識,確保型別不被輕易構造
};

function createEvenArray<T>(data: T[]): EvenArray<T> {
  if (data.length % 2 !== 0) {
    throw new Error("Array must have an even number of elements.");
  }
  // 手動新增一個唯一的象徵性屬性,這一步在實際應用中可能不需要,僅為了演示如何增強型別
  // (注意:在真實場景中,這樣的屬性通常不新增,因為它是內部邏輯的一部分)
  // data._customBrand = Symbol(); // 這一行在實際程式碼中會報錯,因為陣列字面量是不可變的
  return data as EvenArray<T>; // 強制型別斷言,因為我們確信data符合條件
}

// 使用
const evenArray = createEvenArray([1, 2, 3, 4]); // 正確
// const oddArray = createEvenArray([1, 2, 3]); // 錯誤,編譯時檢查不透過

console.log(evenArray);

需要注意的是,直接在型別系統層面完全阻止非偶數長度的陣列被構造為 EvenArray 型別是比較困難的,因為TypeScript的型別系統在執行時是被擦除的,它不能動態檢查陣列長度。
上述程式碼中的 createEvenArray 函式透過在函式體內進行檢查並在不符合條件時丟擲錯誤來實現約束,同時使用型別斷言來標記透過檢查的資料。
但是,嘗試給陣列例項新增一個自定義屬性(如 _customBrand)來增強型別標識在實際中行不通,因為陣列字面量是不可變的,你不能給它新增屬性。

因此,最佳實踐是透過函式或類的構造邏輯來實施這類約束,並利用型別系統儘可能提前捕捉錯誤,而不是直接在型別定義層面上實現完美的靜態驗證。

相關文章