前言

在 TypeScript 的類型系統中,有四種特殊類型經常讓人混淆:undefinednullunknownnever。它們各自有不同的語意和使用場景,正確理解這些類型是寫出型別安全程式碼的基礎。

這篇文章將逐一介紹這四種類型的特點與使用方式,並透過實際範例來說明它們的差異。最後會整理一份最佳實踐與常見注意事項,幫助你在日常開發中做出正確的選擇。

undefined

undefined 代表變數已宣告但尚未賦值,是 JavaScript 中未初始化變數的默認值。

undefined 的特點

  • 表示變數存在但沒有值
  • 是變數的預設狀態
  • 常用於可選參數

undefined 的使用場景

以下範例展示 undefined 在可選屬性、未初始化變數和可選參數中的應用:

// 可選屬性
interface User {
  name: string;
  age?: number; // 可能是 undefined
}

// 未初始化的變數
let userInput: string | undefined;

// 可選參數
function greet(name?: string) {
  console.log(name ? `Hello ${name}` : "Hello");
}

在這個範例中:

  1. age? 表示該屬性是可選的,未提供時值為 undefined
  2. userInput 明確宣告可能為 undefined
  3. name? 參數在未傳入時自動為 undefined

null

null 表示一個刻意被設置為「空值」的變數,與 undefined 不同,它需要明確賦值。

null 的特點

  • 表示刻意的空值
  • 需要明確賦值
  • 用於表示當前無效或不可用的狀態

null 的使用場景

以下範例展示 null 在狀態管理和 API 響應中的應用:

// 表示不存在的狀態
class UserService {
  private currentUser: User | null = null;

  logout() {
    this.currentUser = null; // 明確設置空值
  }
}

// API 響應可能為空
interface ApiResponse {
  data: Data | null;
  error: Error | null;
}

在這個範例中:

  1. currentUser 使用 null 表示「目前沒有登入的使用者」
  2. API 響應中的 dataerror 使用 null 表示「該欄位目前沒有值」

unknown

unknown 是 TypeScript 中最安全的「不確定」類型,比 any 更嚴格。

unknown 的特點

  • any 更安全
  • 需要類型檢查才能使用
  • 不能直接訪問屬性或調用方法

unknown 的使用場景

以下範例展示如何搭配類型檢查和類型守衛來安全地處理 unknown 類型:

// API 響應處理
function processApiResponse(response: unknown) {
  // 需要類型檢查
  if (typeof response === "string") {
    console.log(response.toUpperCase());
  }

  // 使用類型守衛
  if (isUser(response)) {
    console.log(response.name);
  }
}

// 類型守衛
function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "name" in value;
}

在這個範例中:

  1. response 的類型是 unknown,不能直接使用
  2. 透過 typeof 進行類型檢查後,TypeScript 會自動收窄類型
  3. 自訂類型守衛 isUser() 讓我們可以安全地存取 User 的屬性

never

never 表示永遠不會發生或永遠不會有返回值的類型。

never 的特點

  • 表示不可能的狀態
  • 用於永不返回的函數
  • 用於完整性檢查(exhaustive check)
  • 在類型運算中作為過濾器

never 的使用場景

以下範例展示 never 在錯誤拋出、完整性檢查和類型運算中的應用:

// 永不返回的函數
function throwError(message: string): never {
  throw new Error(message);
}

// 完整性檢查
type Color = "red" | "green" | "blue";

function handleColor(color: Color) {
  switch (color) {
    case "red":
      return "處理紅色";
    case "green":
      return "處理綠色";
    case "blue":
      return "處理藍色";
    default:
      // 確保所有可能的值都被處理
      const exhaustiveCheck: never = color;
      return exhaustiveCheck;
  }
}

// 類型運算
type NonNullable<T> = T extends null | undefined ? never : T;

在這個範例中:

  1. throwError() 永遠不會正常返回,所以回傳類型是 never
  2. default 分支利用 never 確保所有 Color 值都被處理過——如果未來新增了顏色卻忘了處理,TypeScript 會報錯
  3. NonNullable 利用 never 在條件類型中過濾掉 nullundefined

類型比較

類型用途示例場景
undefined未初始化的變數可選參數,未賦值的變數
null明確的空值清除狀態,表示不存在
unknown不確定的類型API 響應,外部數據
never不可能的類型異常處理,完整性檢查

最佳實踐

undefined vs null 的選擇

  • 優先使用 undefined 表示「可能不存在」
  • 使用 null 表示「明確的空值」
  • 在同一個專案中保持一致性

型別檢查方式

// 檢查 null
if (value !== null) {
}

// 檢查 undefined
if (value !== undefined) {
}
if (typeof value !== "undefined") {
}

// 檢查 unknown
if (typeof value === "string") {
}

使用 unknown 而不是 any

  • unknown 強制你進行類型檢查,提供更好的類型安全性
  • 適合處理外部數據,例如 API 響應或第三方套件回傳值

使用 never 進行完整性檢查

  • switch 語句中確保處理所有可能的值
  • 在類型運算中過濾不可能的情況

注意事項

避免使用 == 進行空值檢查

// ❌ 不推薦:會同時檢查 null 和 undefined
if (value == null) {
}

// ✅ 推薦:明確檢查
if (value === null) {
}
if (value === undefined) {
}

unknown 類型的值需要類型檢查

function process(value: unknown) {
  // ❌ 錯誤:不能直接使用
  value.toString();

  // ✅ 正確:先進行類型檢查
  if (typeof value === "string") {
    value.toString();
  }
}

never 類型的正確使用

// 用於確保處理所有可能的情況
function assertNever(x: never): never {
  throw new Error("Unexpected object: " + x);
}

總結

TypeScript 的四種特殊類型各有明確的語意和適用場景:

  • 🔹 undefined:變數已宣告但尚未賦值,適用於可選參數和屬性
  • 🔹 null:刻意設置的空值,適用於明確清除狀態的場景
  • 🔒 unknown:比 any 更安全的不確定類型,強制進行類型檢查後才能使用
  • 🚫 never:不可能發生的類型,適用於完整性檢查和類型過濾

記住兩個核心原則:優先使用 unknown 取代 any 來處理外部數據,以及善用 never 搭配 switch 進行完整性檢查,能讓你的 TypeScript 程式碼更加型別安全。

參考資料