跳至主要內容
Skip to content

TypeScript 類型斷言與 as const

有時候你比 TypeScript 更了解某個值的類型。類型斷言讓你告訴編譯器「相信我」。


一、 類型斷言基礎

1.1 為什麼需要斷言?

typescript
// TypeScript 不知道 getElementById 回傳什麼元素
const canvas = document.getElementById('myCanvas')
// 類型:HTMLElement | null

// 你知道它是 canvas,但 TypeScript 不知道
// canvas.getContext('2d')  // 錯誤!

// 使用斷言
const canvas2 = document.getElementById('myCanvas') as HTMLCanvasElement
canvas2.getContext('2d') //

1.2 兩種語法

typescript
// 語法一:as(推薦)
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement

// 語法二:尖括號(在 JSX 中不能用)
const canvas = <HTMLCanvasElement>document.getElementById('myCanvas')

TIP

推薦使用 as 語法,因為它在 JSX/TSX 中也能使用。


二、 常見使用場景

2.1 DOM 元素

typescript
const input = document.querySelector('input') as HTMLInputElement
console.log(input.value)

const form = document.getElementById('form') as HTMLFormElement;
form.submit();

const btn = document.querySelector('.btn') as HTMLButtonElement
btn.disabled = true

2.2 事件處理

typescript
function handleClick(event: Event) {
  const target = event.target as HTMLButtonElement
  console.log(target.textContent)
}

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement
  console.log(target.value)
}

2.3 API 回應

typescript
interface User {
  id: number
  name: string
}

async function fetchUser() {
  const response = await fetch('/api/user')
  const data = (await response.json()) as User
  return data
}

三、 非空斷言(!)

3.1 基本用法

typescript
// TypeScript 認為可能是 null
const element = document.getElementById('app')
// 類型:HTMLElement | null

// 使用非空斷言
const element2 = document.getElementById('app')!
// 類型:HTMLElement

// 或在使用時斷言
element!.textContent = 'Hello'

3.2 可選鏈 vs 非空斷言

typescript
interface User {
  name: string
  address?: {
    city: string
  }
}

const user: User = { name: 'John' }

// 可選鏈(安全)
console.log(user.address?.city) // undefined

// 非空斷言(危險!如果 address 是 undefined 會報錯)
// console.log(user.address!.city)  // Runtime Error!

WARNING

非空斷言很危險!只在你 100% 確定值存在時使用。


四、 const 斷言

4.1 基本用法

typescript
// 沒有 as const
const config = {
  api: 'https://api.example.com',
  timeout: 5000,
}
// 類型:{ api: string; timeout: number }

// 使用 as const
const config2 = {
  api: 'https://api.example.com',
  timeout: 5000,
} as const
// 類型:{ readonly api: 'https://api.example.com'; readonly timeout: 5000 }

4.2 陣列與 as const

typescript
// 沒有 as const
const colors = ['red', 'green', 'blue']
// 類型:string[]

// 使用 as const
const colors2 = ['red', 'green', 'blue'] as const
// 類型:readonly ['red', 'green', 'blue']

// 可以用來建立聯合類型
type Color = (typeof colors2)[number] // 'red' | 'green' | 'blue'

4.3 函式參數

typescript
function request(config: { method: 'GET' | 'POST'; url: string }) {
  // ...
}

// 沒有 as const
const config = { method: 'GET', url: '/api' }
// request(config)  // 錯誤!method 是 string 不是 'GET' | 'POST'

// 使用 as const
const config2 = { method: 'GET', url: '/api' } as const
request(config2) // OK

五、 雙重斷言

5.1 何時需要?

typescript
// 有時候 TypeScript 不允許直接斷言
const x = 'hello' as number // 錯誤!string 不能斷言為 number

// 使用雙重斷言(先斷言為 unknown)
const y = 'hello' as unknown as number // 可以,但很危險!

5.2 為什麼要避免?

typescript
// 雙重斷言繞過了類型檢查
const user = { name: 'John' } as unknown as { id: number }
// user.id 是 undefined,但 TypeScript 認為是 number!

console.log(user.id.toFixed()) //  Runtime Error!

CAUTION

雙重斷言極度危險,幾乎永遠不該使用。如果你需要它,說明類型設計有問題。


六、 satisfies 運算子

6.1 基本用法(TypeScript 4.9+)

typescript
// 傳統方式:失去字面量類型
const config: Record<string, string | number> = {
  api: 'https://api.example.com',
  timeout: 5000,
}
config.api.toUpperCase() // 錯誤!可能是 number

// 使用 satisfies:保留字面量類型
const config2 = {
  api: 'https://api.example.com',
  timeout: 5000,
} satisfies Record<string, string | number>

config2.api.toUpperCase() // OK,api 確定是 string
config2.timeout.toFixed() // OK,timeout 確定是 number

6.2 vs 類型標註

typescript
type Colors = Record<string, [number, number, number]>

// 類型標註:失去鍵的字面量類型
const colors: Colors = {
  red: [255, 0, 0],
  green: [0, 255, 0],
}
// colors.red 的類型是 [number, number, number]
// colors.blue 也不會報錯(undefined)

// satisfies:保留鍵的類型
const colors2 = {
  red: [255, 0, 0],
  green: [0, 255, 0],
} satisfies Colors
// colors2.red 可以正常存取
// colors2.blue  // 錯誤!不存在

七、 最佳實踐

7.1 避免過度使用斷言

typescript
// 過度斷言
const user = {} as User
user.name = 'John' // 危險!

// 正確做法
const user: User = {
  name: 'John',
  email: 'john@example.com',
}

7.2 使用類型守衛替代

typescript
function process(value: unknown) {
  // 直接斷言
  const str = value as string

  // 類型守衛
  if (typeof value === 'string') {
    console.log(value.toUpperCase())
  }
}

7.3 必要時才用斷言

typescript
// DOM 操作是斷言的合理場景
const canvas = document.getElementById('canvas') as HTMLCanvasElement

// API 回應也是合理場景(配合驗證更好)
async function fetchUser(): Promise<User> {
  const response = await fetch('/api/user')
  const data = await response.json()
  // 最好加上驗證
  if (isUser(data)) {
    return data
  }
  throw new Error('Invalid data')
}

總結

語法用途安全性
as Type類型斷言
!非空斷言
as const常量斷言
as unknown as T雙重斷言極低
satisfies類型檢查 + 保留推論

> **使用原則**:

  • 優先使用類型守衛
  • 必要時才用斷言
  • 避免非空斷言和雙重斷言
  • 善用 as constsatisfies

進階挑戰

  1. querySelector 加上正確的類型斷言:
typescript
const form = document.querySelector('#login-form');
const input = document.querySelector('input[name='email']')
const button = document.querySelector('button[type='submit']')
  1. 使用 as const 建立一個設定物件,並從中提取類型:
typescript
const STATUS = {
  PENDING: 'pending',
  SUCCESS: 'success',
  ERROR: 'error'
} as const

type Status = ???  // 提取聯合類型

延伸閱讀與資源