藥不藥來當工程師一個不務正業的藥師,誤打誤撞來當了前端工程師。斜槓工程師的人生!

TypeScript 5.5+ Inferred Type Predicates:告別手動標註 `item is string`

發佈於 最後更新於
8 min read

在開發 Vue 組件時,我遇到了一個有趣的類型推斷問題,這讓我深入研究了 TypeScript 5.5 引入的 Inferred Type Predicates 功能。

問題的起源

在開發一個下拉選單組件時,我需要處理混合的資料結構:

typescript
interface Option {
  label: string
  value: string
}

interface GroupedOption {
  groupLabel: string
  options: Option[]
}

// 展開後的結構:(Option | string)[]
// string 代表分組標籤,Option 代表實際選項
const expandedOptions = [
  '水果', // 分組標籤
  { label: '蘋果', value: 'apple' }, // 選項
  { label: '香蕉', value: 'banana' }, // 選項
  '蔬菜', // 分組標籤
  { label: '胡蘿蔔', value: 'carrot' } // 選項
]

在 Vue 模板中,我需要判斷每個項目是分組標籤還是選項:

vue
<template>
  <template v-for="(item, index) in expandedOptions" :key="index">
    <div v-if="typeof item === 'string'" class="group-title">
      {{ item }}
    </div>
    <DropdownOption v-else :option="item" />
  </template>
</template>

傳統的解決方案

為了讓程式碼更語意化,我通常會建立一個 helper function1

typescript
// 傳統寫法:需要手動標註 type predicate
function isGroupLabel(item: Option | string): item is string {
  return typeof item === 'string'
}

然後在模板中使用:

vue
<div v-if="isGroupLabel(item)" class="group-title">
  {{ item }}
</div>

TypeScript 5.5 的革命性改進

TypeScript 5.5 引入了 Inferred Type Predicates2,讓編譯器能夠自動推斷類型謂詞!

自動推斷的條件

根據官方文檔2,TypeScript 會自動推斷 type predicate,當函數滿足以下條件:

  1. 沒有明確的回傳類型註解
  2. 只有單一 return 語句且沒有隱式回傳
  3. 不會修改參數
  4. 回傳與參數相關的 boolean 表達式

簡化後的寫法

typescript
// TypeScript 5.5+ 自動推斷為: (item: Option | string) => item is string
function isGroupLabel(item: Option | string) {
  return typeof item === 'string' // 不需要手動標註!
}

IDE 會自動顯示推斷出的類型:

typescript
// 推斷結果:function isGroupLabel(item: string | Option): item is string

更多實用範例

這個功能適用於各種常見的類型檢查場景:

typescript
// 數字檢查
const isNumber = (x: unknown) => typeof x === 'number'
// 推斷為:(x: unknown) => x is number

// 非空檢查
const isNonNullish = <T>(x: T) => x != null
// 推斷為:<T>(x: T) => x is NonNullable<T>

// 陣列過濾
const numbers = [1, 2, null, 4, undefined, 6]
const validNumbers = numbers.filter(x => x !== null && x !== undefined)
// validNumbers 的類型自動推斷為 number[]

意外的發現:Arrow Function 也支援!

在實際測試中,我發現了一個有趣的現象。雖然官方文檔提到「沒有隱式回傳」的限制,但現代的 TypeScript + IDE 組合似乎已經支援 arrow function 的隱式回傳:

typescript
// 這樣也能正確推斷!
const isGroupLabel = (item: Option | string) => typeof item === 'string'
// IDE 顯示:(item: string | Option) => item is string

什麼是「隱式回傳」?

隱式回傳是指函數沒有明確寫 return,但仍然會回傳值的情況:

typescript
// ✅ 明確回傳(符合官方條件)
function isGroupLabel(item: Option | string) {
  return typeof item === 'string'
}

// ❌ 隱式回傳(官方說不支援,但實際上可以!)
const isGroupLabel = (item: Option | string) => typeof item === 'string'

這可能是 TypeScript 團隊在後續版本中靜默改進的結果,讓這個功能比官方文檔描述的更加強大。

實際應用的好處

1. 程式碼更簡潔

typescript
// 之前
function isGroupLabel(item: Option | string): item is string {
  return typeof item === 'string'
}

// 現在
function isGroupLabel(item: Option | string) {
  return typeof item === 'string'
}

2. 類型安全不減

typescript
if (isGroupLabel(item)) {
  // TypeScript 知道這裡 item 是 string
  item.toUpperCase() // ✅ 正常工作
}

3. 更好的開發體驗

  • IDE 自動完成更準確
  • 錯誤檢查更及時
  • 重構更安全

注意事項

Truthiness 檢查的陷阱

並非所有的 boolean 回傳都會被推斷為 type predicate:

typescript
// ❌ 這不會推斷為 type predicate
const isValid = (score: number | undefined) => !!score

// 原因:score 為 0 時回傳 false,但 0 仍然是 number
// 如果回傳 false,score 可能是 undefined 或 0

正確的寫法:

typescript
// ✅ 這會正確推斷
const isValid = (score: number | undefined) => score !== undefined

向後相容性

如果你的專案升級到 TypeScript 5.5+,某些程式碼可能會因為更精確的類型推斷而出現錯誤:

typescript
// 之前:nums 類型為 (number | null)[]
// 現在:nums 類型為 number[]
const nums = [1, 2, 3, null, 5].filter(x => x !== null)

nums.push(null) // TypeScript 5.5+ 會報錯

解決方法是明確標註類型:

typescript
const nums: (number | null)[] = [1, 2, 3, null, 5].filter(x => x !== null)

命名的學問:flattenedOptions vs expandedOptions

在這次的開發過程中,我也學到了命名的重要性。對於處理分組選項的不同方式:

typescript
// flattenedOptions:扁平化 - 移除分組結構,只保留選項
const flattenedOptions = [
  { label: '蘋果', value: 'apple' },
  { label: '香蕉', value: 'banana' },
  { label: '胡蘿蔔', value: 'carrot' }
]

// expandedOptions:展開 - 保留分組標籤並展開結構
const expandedOptions = [
  '水果', // 分組標籤
  { label: '蘋果', value: 'apple' },
  { label: '香蕉', value: 'banana' },
  '蔬菜', // 分組標籤
  { label: '胡蘿蔔', value: 'carrot' }
]

好的命名能讓程式碼更容易理解和維護。

總結

TypeScript 5.5 的 Inferred Type Predicates 是一個實用的改進,讓我們能夠:

  • 寫更少的程式碼:不需要手動標註 type predicate
  • 保持類型安全:自動推斷的類型謂詞功能完整
  • 提升開發體驗:IDE 支援更好,錯誤檢查更準確

而且實際使用中發現,這個功能比官方文檔描述的更強大,連 arrow function 的隱式回傳都能正確處理。

如果你的專案使用 TypeScript 5.5+,不妨檢查一下現有的 type predicate 函數,看看哪些可以簡化。這個小改進能讓程式碼更簡潔,同時保持完整的類型安全!

延伸閱讀:

Reference

  1. Using type predicates - TypeScript Handbook - TypeScript 官方文件說明 Type Predicates 的基本概念與用法。
  2. Inferred Type Predicates - TypeScript 5.5 Release Notes - TypeScript 官方文件說明 Inferred Type Predicates 的功能與使用條件。 2
Copyright 2020-2026 - AzureBlue ALL RIGHTS RESERVED.