类型断言

类型断言用于手动指定值的类型,告诉编译器"我知道这个值是什么类型"。

语法形式

as 语法

let value: unknown = "hello"

let length: number = (value as string).length
console.log(length)  // 5

尖括号语法

let value: unknown = "hello"

let length: number = (<string>value).length
console.log(length)  // 5

注意:尖括号语法在 JSX 中与 React 语法冲突,推荐使用 as 语法。

使用场景

缩小类型范围

let value: unknown = "hello world"

console.log((value as string).toUpperCase())  // "HELLO WORLD"

DOM 元素

const input = document.getElementById("email") as HTMLInputElement

console.log(input.value)

联合类型断言

let value: string | number = "hello"

let str: string = value as string
console.log(str.toUpperCase())  // "HELLO"

类型转换

let value: any = "hello"

let num: number = value as number  // 编译通过,运行时可能出错
console.log(num.toFixed(2))  // 运行时报错

双重断言

极端情况下可以使用双重断言:

let value: string = "hello"

let num: number = value as unknown as number

console.log(num)  // "hello"(字符串,不是数字)

双重断言很危险,尽量避免使用。

const 断言

使用 as const 将类型锁定为字面量:

let name = "张三" as const
// 类型为 "张三",而不是 string

let arr = [1, 2, 3] as const
// 类型为 readonly [1, 2, 3]

let obj = { name: "张三", age: 25 } as const
// 类型为 { readonly name: "张三"; readonly age: 25 }

实际应用

const config = {
  endpoint: "/api",
  timeout: 5000
} as const

// 类型为 { readonly endpoint: "/api"; readonly timeout: 5000 }

元组转换

const points = [10, 20] as const
// 类型为 readonly [10, 20]

const [x, y] = points

非空断言

使用 ! 断言值不为 null 或 undefined:

let value: string | null = "hello"

console.log(value!.toUpperCase())  // "HELLO"

DOM 元素

const element = document.getElementById("app")!

element.innerHTML = "hello"

实际应用

API 响应处理

interface User {
  id: number
  name: string
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`)
  const data = await response.json()
  return data as User
}

事件处理

document.addEventListener("click", (event) => {
  const target = event.target as HTMLButtonElement
  console.log(target.textContent)
})

类型守卫结合

function isString(value: unknown): value is string {
  return typeof value === "string"
}

function process(value: unknown): void {
  if (isString(value)) {
    console.log(value.toUpperCase())
  } else {
    console.log((value as number).toFixed(2))
  }
}

枚举值断言

enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE"
}

function getStatus(value: string): Status {
  return value as Status
}

const status = getStatus("ACTIVE")
console.log(status === Status.Active)  // true

注意事项

断言不是转换

let value: string = "hello"

let num: number = value as number  // 编译通过

num.toFixed(2)  // 运行时报错:num 实际是字符串

避免滥用 any

// 不推荐
let value = data as any

// 推荐
let value = data as { name: string; age: number }

类型检查优先

// 不推荐:直接断言
function process(value: unknown) {
  return (value as string).toUpperCase()
}

// 推荐:先检查类型
function process(value: unknown) {
  if (typeof value === "string") {
    return value.toUpperCase()
  }
  throw new Error("值不是字符串")
}

运行时安全

类型断言只影响编译时,不影响运行时:

let value: any = "hello"

let num: number = value as number

console.log(typeof num)  // "string"

断言 vs 类型守卫

方式安全性说明
类型断言编译器信任你的判断
类型守卫运行时检查类型
// 类型断言
const element = document.getElementById("app") as HTMLDivElement

// 类型守卫
const element = document.getElementById("app")
if (element instanceof HTMLDivElement) {
  // 安全使用
}