TypeScript unknown vs any vs never
any、unknown、never 是 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 註解)
進階挑戰
- 將以下
any改為更安全的類型:
typescript
function processResponse(response: any) {
if (response.success) {
return response.data
}
throw new Error(response.error)
}- 為以下函式加上適當的回傳類型:
typescript
function divide(a: number, b: number) {
if (b === 0) {
throw new Error('Cannot divide by zero')
}
return a / b
}- 實作一個安全的型別守衛:
typescript
function isUser(value: unknown): value is User {
// 實作...
}