網站搜尋

TypeScript - 如何讓函數參數陣列不相交?


總的來說,我的目標是製作一個函數,該函數接受兩個字串陣列作為參數,且第二個陣列不應包含第一個陣列的元素:

function doSomething<
    const T extends string,
    const K extends StringExcept<T>
>(
    firstArray: T[],
    secondArray: K[]
) {}

doSomething(['a', 'b'], ['c', 'b'])  // 'b' in the second array should cause type error

首先,我認為,我需要一種類型,比如 type StringExcept<T>,它應該這樣運作:

type NotAbc = StringExcept<'abc'>;
const notAbcArray:NotAbc[] = [
    'any string',   // okay
    '123',          // okay
    'abc'           // not allowed
]

我嘗試過使用 Exclude<> 類型,例如 Exclude<string, 'abc'>,但這樣並不起作用,結果只是 string 類型。我也嘗試過不同的變體,例如 T extends K ? never : T 語句,但後來我發現 Exclude 類型實際上就是做相同的事情。

然後我找到了這個問題,它表示像 StringExcept 這樣的類型在 TS 現階段是不可能的(但如果有任何更新,我想知道)。我也看過這個實驗性功能,但現在已經關閉了。

那麼,有沒有辦法讓我的 doSomething(firstArray, secondArray) 函數使用像 StringExcept 這樣的類型,或者不使用?

答案#1

你可以在 secondArray 本身的類型中使用 Exclude 工具類型:

function doSomething<
  const T extends string,
  const U extends string
>(
  firstArray: T[],
  secondArray: Exclude<U, T>[]
) { }

這將導致 U 被推斷為 secondArray 的元素,然後再對 Exclude<U, T> 進行 檢查:

doSomething(['a', 'b'], ['c', 'd']); // okay

doSomething(['a', 'b'], ['c', 'b']); // error! 
// -------------------------> ~~~
// Type '"b"' is not assignable to type '"c"'.

這之所以有效,是因為 T 和 U 都會(希望)是字串字面量類型的聯集,而 Exclude 可以過濾這樣的聯集(而它無法過濾像 string 這樣的非聯集類型)。

如果我們能把 Exclude<U, T> 放在 U 的約束中會更好,就像 const U extends string & Exclude<U, T> 一樣,但編譯器會認為這是非法的循環。因此我們可以把它移到參數類型中。

程式碼的 Playground 連結