網站搜尋

typescript - 在使用 case switch 和泛型時,如何修復 ts(2322)?


我一直在嘗試使用 switch case 來映射值,使用 Typescript。例如,要獲取數字的字串版本。

function getNumber(id) {
  switch (id) {
    case 1:
      return 'one';
    case 2:
      return 'two';
    case 3:
      return 'three';
  }
}

我想使用 Typescript 確保傳入的是有效的鍵,因此我嘗試了以下介面並用它來提取允許的鍵

interface Numbers {
  1: 'one';
  2: 'two';
  3: 'three';
}

function getNumber<K extends keyof Numbers>(id: K) {
}

到目前為止一切順利!事實上,如果我嘗試 getNumber(4),我會得到一個錯誤。

這個問題出在返回類型。我期望返回 Numbers[K] | void。

function getNumber<K extends keyof Numbers>(id: K): Numbers[K] | void {
  switch (id) {
    case 1:
      return 'one';
    case 2:
      return 'two';
    case 3:
      return 'three';
  }
}

但相反,我收到錯誤 Type '"one"' is not assignable to type 'void | Numbers[K]'.ts(2322)。

我在使用 TS 3.5.1 時看到了這個問題,而在之前的版本中我沒有看到這個問題。

有什麼想法嗎?

答案#1

這個問題是因為一個拉取請求 (pull request) 被包含在 3.5 版本中而發生的。已知這是一個破壞性變更。簡而言之,如果你有一個變數 x 類型是 {a: string, b: number},一個值 k 類型是 "a" | "b",以及一個值 v 類型是 string | number,TypeScript 過去允許你寫 x[k]=v,而現在則不允許了。過去,如果 k 碰巧是 "a" 且 v 碰巧是 1,編譯器會愉快地給你一個肯定,然後你在運行時評估 x 時就會出錯。 a.charAt(0)。根據管理 --strictFunctionTypes 編譯器選項以及改善調用函數類型聯合的相同規則,唯一可以允許的 安全 做法是所涉及類型的 交集。因此,你只能在 w 的類型是 string & number 而不是 string | number 的情況下寫 x[k]=w。對於這些原始類型,沒有同時屬於 string 和 number 的值,因此實際上它不會讓你給 x[k] 賦值而不報錯。

你的程式碼與最近提交的一個 Github 問題中的程式碼非常相似,該問題被關閉,原因是「按預期運作」。對於像你這樣的情況來說這很不幸,但這裡的解決方案與你每次知道某件事是安全的,但編譯器不這麼認為時所使用的解決方案相同:類型斷言:

  function getNumber<K extends keyof Numbers>(id: K): Numbers[K] {
    switch (id) {
      case 1:
        return "one" as Numbers[K]; // assertion
      case 2:
        return "two" as Numbers[K]; // assertion
      case 3:
        return "three" as Numbers[K]; // assertion
    }
    throw new Error("this can't happen"); // ensure the compiler that all paths return
  }

另一個你可以用於函數的故意不嚴格工具是重載。編譯器允許你為函數內部的型別檢查提供一個「更寬鬆」的實作簽名,並為函數呼叫位址的型別檢查提供一個「更嚴格」的呼叫簽名(或一組簽名)。這種差異給了你克服這個特定錯誤所需的空間。請注意,這在道德上等同於型別斷言,但不需要在每個 return 語句中輸入那麼多內容:

  // call signature: strict
  function getNumber<K extends keyof Numbers>(id: K): Numbers[K];
  // impl signature: loose
  function getNumber(id: keyof Numbers): Numbers[keyof Numbers] {
    switch (id) {
      case 1:
        return "one";
      case 2:
        return "two";
      case 3:
        return "three";
    }
  }

最後,我不知道這是否符合你的使用情況,你可以完全放棄 switch,改用正確類型的物件並使用索引訪問。這在 TS3.5 中仍然是可接受的:

    function getNumber<K extends keyof Numbers>(id: K): Numbers[K] {
      const numbers: Numbers = { 1: "one", 2: "two", 3: "three" };
      return numbers[id];
    }

好的,希望這對你有幫助。祝你好運!

程式碼連結