类型谓詞是TypeScript中一個有趣的語法特點。雖然它們出現在與返回型別注釋相同的位置,但它們看起來更像短句而不是典型的注釋。這使您能夠更的控制類型檢查。

隨著TypeScript 5.5的發布,使用類型謂詞變得更加直觀,因為在许多情況下它可以自動推斷它們。但如果您在稍微舊的代碼庫中导航,您可能會更經常遇到手寫的類型謂詞。

在本文中,我們將简要探索什麼是類型謂詞以及它們有什麼用處。讓我們從它們解決的問題開始要看。

問題

我相信了解類型謂詞的用途最好的方式是注意我們沒有它們時会出现的問題:

function isString(value: unknown): boolean {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

在此示例中,isString的返回型別被設定為boolean,我們在稱為padLeft的函數中使用它來向輸入字符串的左邊添加填充。padding可以是給定的字符串或指定空格字符的數量。

你可能会想为什么我将返回类型硬编码为boolean。这是为了说明问题。如果你不添加任何返回类型注解并使用最新版本的TypeScript,你在这里不会注意到任何问题。现在,请忍耐片刻,我们将很快讨论与版本相关的差异。

该函数在运行时将平稳工作,但是TypeScript无法对isString进行任何类型缩小。因此,padding的类型在if语句内外仍然是string | number。这与repeat对其第一个参数的期望产生冲突,导致类型错误。

解决方案:引入类型谓词

即使你对谓词这个术语不熟悉,你可能之前已经使用过它们。在编程中,谓词是简单地返回布尔值以回答是/否问题的函数。几个JavaScript内置的数组方法,如filterfindeverysome,使用谓词来帮助做出决策。

类型谓词是一种使谓词在类型缩小方面更有用的方式。我们可以通过使用类型谓词作为返回类型来解决问题:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

这里的类型谓词是value is string。它表达了三个意思:

  • 该函数是一个谓词。因此,如果尝试返回除布尔值之外的任何内容,TypeScript将显示错误。

  • 如果返回 true,則 value 是一個字符串類型。

  • 如果返回 false,則 value 不是字符串類型。

类型断言让你创建自定义类型守卫。类型守卫是逻辑检查,让你细化类型到更具体的类型,即缩小它们。所以,上面的函数也是一种自定义类型守卫。

以下是完整的代码:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string
  }
  return " ".repeat(padding) + input;
                 //   ^
                 // number
}

在这里,TypeScript正确地在if语句内和外细化了padding的类型。

现在让我们简要地看看在TypeScript 5.5之前类型断言是如何工作的,以及这个版本做了哪些改进。

Type Predicates Before TypeScript 5.5

在我们的上一个例子中,如果我们不指定任何返回类型,它将被推断为boolean:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // string | number
}

因此,我們得到了與我們手動寫的返回類型 boolean 相同的錯誤。這裡是 TypeScript 開發環境的連結 供上述代碼片段使用。點擊函數或變量並悬停,以更好地感受型別。然後看看如何通過寫入型別條件解決問題。

如果我们不指定型別條件,使用如 filter 方法也可能導致不正確的型別侦測:

function isString(value: unknown) {
  return typeof value === "string";
}

const numsOrStrings = [1, 'hello', 2, 'world'];
//      ^
//    strings: (string | number)[]

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: (string | number)[]

現在讓我們看看 TypeScript 5.5 是如何改善這個情況的。

型別條件在 TypeScript 5.5 之後

TypeScript 5.5 的一個主要特點是它能夠通過分析函數體來推斷型別條件。所以如果你使用的是 TypeScript 5.5 或更高版本,你不需要為 isString 的返回類型手動寫入型別條件。TypeScript 會為你完成這個工作,並且如下所示的代碼示例是完全没問題的:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string
  }
  return " ".repeat(padding) + input; // Opps type error here
                 //   ^
                 // number
}

const numsOrStrings = [1, 'hello', 2, 'world'];

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: string[]

const numbers = numsOrStrings.filter((v) => !isString(v));
//      ^
//    numbers: number[]

我還沒有發現不滿意TypeScript自動推斷型別條件的情況。如果你發現了,你總是可以手動寫自己的。

進階學習

在本文中,我們简要探討了TypeScript中的型別條件。如果你对人體学的更多內容和了解邊界案例感興趣,以下是官方指南:

感謝閱讀!下次再見!

封面照片背景來自 Mona EendraUnsplash