Is Operator in Typescript

最近在寫 ts 發現了這個以前沒用過的 operator,筆記一下

Summary

總結來說,is 主要相信程式,as 主要是相信工程師,他們都無法準確檢查到邏輯錯誤

項目 贏家 原因
可維護性 is 邏輯集中管理
型別安全 - 都不驗證邏輯
重構友善 is 連動報錯機制
學習曲線 as 更直觀
工程價值 is 符合 SOLID 原則

is 的價值在於「把型別判斷邏輯變成可管理的單元」,這在大型專案中是巨大的優勢。

適用場景

適合用 as 的場景

  1. 單次使用、邏輯簡單
  2. 你 100% 確定型別(如 JSON.parse() 後)
  3. 原型開發、快速驗證
  4. 與外部無型別定義的 library 互動

適合用 is 的場景

  1. 邏輯會重複使用
  2. 需要在多個模組間共享
  3. 複雜的型別判斷(如區分 Union Type)
  4. 團隊協作、需要程式碼審查
  5. 需要為守衛邏輯寫測試

isas 的區別

決策流程圖

需要型別收窄?
  │
  ├─ 否 → 不需要任何處理
  │
  └─ 是
      │
      ├─ 邏輯只用一次?
      │   └─ 是 → 用 `as`(簡潔)
      │
      ├─ 需要抽離成函式?
      │   └─ 是 → 用 `is`(型別傳播)
      │
      ├─ 邏輯重複 3+ 次?
      │   └─ 是 → 用 `is`(DRY)
      │
      └─ 需要測試型別判斷邏輯?
          └─ 是 → 用 `is`(可測試)

基本比較

比較維度 as (Type Assertion) is (Type Predicate)
基本語法 value as Type (param): param is Type => boolean
使用位置 任何表達式後面 只能用在函式回傳型別
作用時機 立即、單次轉換 定義可復用的型別守衛
型別安全 ❌ 編譯器完全信任你,不驗證 ❌ 編譯器僅檢查型別兼容性,無法驗證邏輯正確性
Runtime 檢查 ❌ 無任何檢查 ✅ 必須配合實際判斷邏輯

程式碼複雜度

維度 as is
Inline 使用 ✅ 簡潔 list.filter(x => !!x) as string[] ⚠️ 略冗長 list.filter((x): x is string => !!x)
抽離函式 ❌ 需額外標註 filter(fn) as Type[] ✅ 自動推斷 filter(guardFn)
多處使用 ❌ 每處都要寫 as ✅ 定義一次,到處使用

可維護性

場景 as is
邏輯變更 ❌ 需修改所有使用處 ✅ 只改函式定義處
型別變更 ⚠️ 必須手動更新每個 as Type ✅ 函式簽名改了,所有呼叫處自動連動報錯
重構安全 ❌ 容易遺漏 ✅ 編譯器協助追蹤
Code Review ⚠️ 審查者需檢查每個 as ✅ 只需審查守衛函式本身

風險比較

風險類型 as is
錯誤的型別斷言 ⚠️ 可用 as unknown as T 或是 as any 繞過,Runtime 爆炸 ⚠️ 直接編譯通過,Runtime 爆炸
邏輯與型別不一致 🔴 高風險 - 分散在各處 🟡 中風險 - 集中在一處
重構時遺漏更新 🔴 高風險 - 編譯器不會提醒 🟢 低風險 - 型別簽名變更會連動報錯
誤用範圍 🔴 可在任何地方亂用 🟢 只能在特定場景使用(函式回傳)

團隊協作

維度 as is
新人理解成本 🟢 低 - 直觀 🟡 中 - 需理解 Type Guard 概念
程式碼審查 🔴 每個 as 都是潛在風險點 🟢 集中審查守衛函式即可
文檔價值 ❌ 無自文檔性 ✅ 函式名稱即文檔 (isValidUser, hasAllFields)
測試便利性 ❌ 無法獨立測試型別邏輯 ✅ 可對守衛函式寫單元測試

效能比較

維度 as is
編譯後程式碼 ✅ 完全消失,零成本 ✅ 完全消失,零成本
Runtime 開銷 ✅ 無 ✅ 無(只是普通函式呼叫)
Bundle Size ✅ 無影響 ⚠️ 若守衛函式複雜可能增加少量
cmd + /