跳至主要內容
Skip to content

TypeScript unknown vs any vs never

anyunknownnever 是 TypeScript 中最特殊的三種類型。本篇將深入解析它們的差異與使用場景。


一、 any:逃生艙口

1.1 什麼是 any?

any 表示「任何類型」,完全關閉類型檢查:

typescript
let value: any = 'hello'
value = 42
value = { name: 'John' }
value = [1, 2, 3]

// 什麼操作都可以,不會報錯
value.foo.bar.baz() // 編譯通過(執行時會錯)
value() // 編譯通過
value[0] // 編譯通過

1.2 any 的問題

typescript
function processData(data: any) {
  // 失去了所有類型安全
  data.toUpperCase() // 如果 data 是 number?
  data.map((x) => x) // 如果 data 不是陣列?
}

// 錯誤不會在編譯時被發現
processData(42) // 編譯通過,執行時報錯

1.3 何時使用 any?

場景說明
遷移舊專案暫時使用,逐步加入類型
第三方庫無類型先用 any,再補類型
快速原型驗證想法,之後補類型

> **盡量避免使用 any!** 它會讓 TypeScript 失去意義。


二、 unknown:安全的 any

2.1 什麼是 unknown?

unknown 是「未知類型」,可以接受任何值,但必須先檢查才能使用:

typescript
let value: unknown = 'hello'
value = 42
value = { name: 'John' }

// 不能直接使用!
// value.toUpperCase()  // 錯誤
// value.foo           // 錯誤
// value()             // 錯誤

// 必須先檢查類型
if (typeof value === 'string') {
  console.log(value.toUpperCase())
}

if (typeof value === 'number') {
  console.log(value.toFixed(2))
}

2.2 any vs unknown

typescript
// any:無限制
let anyValue: any = getData()
anyValue.foo.bar() // 編譯通過

// unknown:有限制
let unknownValue: unknown = getData()
// unknownValue.foo.bar()  // 錯誤

// 必須先收窄
if (unknownValue && typeof unknownValue === 'object' && 'foo' in unknownValue) {
  // 現在可以安全存取
}

2.3 使用場景

typescript
// 1. API 回應
async function fetchData(): Promise<unknown> {
  const response = await fetch('/api/data')
  return response.json() // JSON 可能是任何東西
}

// 2. 錯誤處理
function handleError(error: unknown) {
  if (error instanceof Error) {
    console.log(error.message)
  } else if (typeof error === 'string') {
    console.log(error)
  } else {
    console.log('Unknown error')
  }
}

// 3. 使用者輸入
function processInput(input: unknown) {
  if (typeof input === 'string') {
    return input.trim()
  }
  throw new Error('Expected string input')
}

三、 never:不可能的類型

3.1 什麼是 never?

never 表示「永遠不會發生」的類型:

typescript
// 永遠不會正常返回的函式
function throwError(message: string): never {
  throw new Error(message)
}

// 無限迴圈
function infiniteLoop(): never {
  while (true) {}
}

3.2 never 的特性

typescript
// never 是所有類型的子類型
let x: never

let a: string = x // (x 不可能有值,所以任何賦值都合法)
let b: number = x // 
let c: any = x // 

// 但沒有類型是 never 的子類型(除了 never 本身)
// x = 'hello'  // 錯誤
// x = 42       // 錯誤

3.3 窮盡檢查

typescript
type Status = 'pending' | 'success' | 'error'

function handleStatus(status: Status) {
  switch (status) {
    case 'pending':
      return 'Loading...'
    case 'success':
      return 'Done!'
    case 'error':
      return 'Failed!'
    default:
      // 如果漏掉了某個 case,這裡會報錯
      const _exhaustive: never = status
      return _exhaustive
  }
}

// 新增狀態時會自動報錯
type Status2 = 'pending' | 'success' | 'error' | 'cancelled'
// handleStatus 會提示漏掉了 'cancelled'

3.4 條件類型中的 never

typescript
// never 在聯合類型中會被過濾
type A = string | never // string
type B = number | never // number

// 用於條件類型過濾
type NonString<T> = T extends string ? never : T

type Result = NonString<string | number | boolean>
// number | boolean(string 被過濾)

四、 void vs undefined vs never

4.1 比較

類型含義使用場景
void沒有回傳值函式不需要回傳
undefined未定義值可能不存在
never不會發生拋出錯誤、無限迴圈

4.2 範例

typescript
// void:函式結束但沒有回傳值
function log(message: string): void {
  console.log(message)
  // 隱式 return undefined
}

// undefined:明確回傳 undefined
function getUndefined(): undefined {
  return undefined
}

// never:函式不會正常結束
function fail(): never {
  throw new Error('Failed')
}

五、 實用模式

5.1 安全的 JSON 解析

typescript
function safeJsonParse(json: string): unknown {
  try {
    return JSON.parse(json)
  } catch {
    return null
  }
}

// 使用時必須檢查
const data = safeJsonParse('{'name': 'John'}')
if (data && typeof data === 'object' && 'name' in data) {
  console.log((data as { name: string }).name)
}

5.2 類型安全的 catch

typescript
async function fetchData() {
  try {
    const response = await fetch('/api/data')
    return await response.json()
  } catch (error: unknown) {
    // TypeScript 4.4+ 預設為 unknown
    if (error instanceof Error) {
      console.error(error.message)
    }
    throw error
  }
}

5.3 斷言函式

typescript
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Expected string')
  }
}

function process(value: unknown) {
  assertIsString(value)
  // value 現在是 string
  console.log(value.toUpperCase())
}

5.4 Branded Types

typescript
type UserId = string & { readonly _brand: unique symbol }

function createUserId(id: unknown): UserId {
  if (typeof id !== 'string') {
    throw new Error('Invalid user ID')
  }
  return id as UserId
}

六、 類型層級

  • unknown 是所有類型的父類型(Top Type)
  • never 是所有類型的子類型(Bottom Type)
  • any 是特殊存在,既是 Top 又是 Bottom(打破規則)

總結

類型可賦值給可接收使用前需要安全性
any任何類型任何值不需要不安全
unknown自己和 any任何值類型檢查安全
never任何類型不可能-安全

> **選擇建議**:

  • 不確定類型?用 unknown
  • 不需要回傳?用 void
  • 會拋出錯誤?用 never
  • 最後手段?用 any(並加 TODO 註解)

進階挑戰

  1. 將以下 any 改為更安全的類型:
typescript
function processResponse(response: any) {
  if (response.success) {
    return response.data
  }
  throw new Error(response.error)
}
  1. 為以下函式加上適當的回傳類型:
typescript
function divide(a: number, b: number) {
  if (b === 0) {
    throw new Error('Cannot divide by zero')
  }
  return a / b
}
  1. 實作一個安全的型別守衛:
typescript
function isUser(value: unknown): value is User {
  // 實作...
}

延伸閱讀與資源