类型守卫与类型断言
2026/7/9大约 5 分钟
类型守卫与类型断言
概述
类型守卫(Type Guard)和类型断言(Type Assertion)是 TypeScript 中处理"编译器不知道但开发者知道"类型信息的两类工具。Go 开发者对类型断言并不陌生(x.(Type)),但 TypeScript 提供了更丰富的类型收窄机制:typeof、instanceof、自定义守卫、可辨识联合(Discriminated Unions)等。
Go 开发者已知
// Go 类型断言
var x interface{} = "hello"
// 安全断言
s, ok := x.(string)
if ok {
fmt.Println(s) // "hello"
}
// 不安全断言(会 panic)
s2 := x.(string)
// 类型 switch
switch v := x.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
default:
fmt.Println("unknown")
}Go 类型处理特点:
- 类型断言是运行时行为,基于
interface{}的动态类型 - 安全断言返回
(value, bool)模式 - 类型 switch 是穷举式的
- 没有编译时的类型守卫机制(TS 特有)
TypeScript 怎么做
typeof 守卫
function process(value: string | number): string {
// typeof 收窄类型
if (typeof value === 'string') {
// 这里 value 被收窄为 string
return value.toUpperCase()
}
// 这里 value 被收窄为 number
return value.toFixed(2)
}instanceof 守卫
class Dog {
bark() { console.log('Woof!') }
}
class Cat {
meow() { console.log('Meow!') }
}
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark() // 收窄为 Dog
} else {
animal.meow() // 收窄为 Cat
}
}自定义守卫(is 关键字)
interface Fish {
swim(): void
}
interface Bird {
fly(): void
}
// 自定义类型守卫
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined
}
function move(pet: Fish | Bird): void {
if (isFish(pet)) {
pet.swim() // 收窄为 Fish
} else {
pet.fly() // 收窄为 Bird
}
}
// 更实用的守卫
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function isNonEmptyArray<T>(value: unknown): value is T[] {
return Array.isArray(value) && value.length > 0
}可辨识联合(Discriminated Unions)
// 可辨识联合 —— 一个公共的 literal 属性作为判别器
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number }
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'rectangle':
return shape.width * shape.height
case 'triangle':
return (shape.base * shape.height) / 2
default:
const _exhaustive: never = shape
return _exhaustive
}
}类型断言(as / <> / !)
// as 断言
const input = document.getElementById('input') as HTMLInputElement
input.value = 'hello'
// <> 断言(JSX 中不能使用)
const input2 = <HTMLInputElement>document.getElementById('input')
// 非空断言 !
function process(name?: string): void {
const s: string = name! // 断言 name 非 null/undefined
}
// 双重断言(极少使用)
const x = ('hello' as unknown) as number差异分析
| 维度 | Go | TypeScript |
|---|---|---|
| 运行时类型检查 | x.(Type) 基于 interface{} | typeof / instanceof 基于 JS 运行时 |
| 编译时类型收窄 | 不支持 | 类型守卫、可辨识联合 |
| 自定义守卫 | 不支持 | pet is Fish 语法 |
| 断言语法 | x.(Type) 或 x.(Type) 安全形式 | as Type 或 <Type> |
| 断言安全性 | 不安全的断言会 panic | 断言不改变运行时行为 |
| 非空断言 | 不支持 | ! 后缀操作符 |
| 可辨识联合 | 不支持(无字面量类型) | 一等公民 |
关键区别:编译时 vs 运行时
TypeScript 的类型检查是编译时的,类型在运行时被抹除。这意味着:
// TS 的类型守卫编译后消失
function isString(x: unknown): x is string {
return typeof x === 'string'
}
// 编译后:
// function isString(x) {
// return typeof x === 'string'
// }
// Go 的类型断言是运行时行为
// v, ok := x.(string) // 运行时检查Go 的类型断言依赖 interface{} 内部存储的动态类型信息,而 TypeScript 的类型守卫依赖 JavaScript 运行时提供的 typeof / instanceof 等操作。
Bad Practice
// Bad: 滥用 as 断言
const data: unknown = fetchData()
const user = data as User // 运行时完全可能不是 User
user.name.toUpperCase() // 运行时崩溃
// Bad: 用 as 逃避类型问题
function add(a: number, b: number): number {
return (a + b) as unknown as number // 毫无意义
}
// Bad: 过度使用非空断言
function process(user?: { name?: string }) {
const name = user!.name! // 如果 user 或 name 是 undefined,运行时崩溃
console.log(name.toUpperCase())
}
// Bad: 用 as 代替类型守卫
function handle(input: string | number) {
(input as string).toUpperCase() // 如果 input 是 number,运行时崩溃
}Best Practice
// Good: 优先类型守卫,次选类型断言
function process(input: string | number): string {
if (typeof input === 'string') {
return input.toUpperCase()
}
return input.toFixed(2)
}
// Good: 使用可辨识联合替代多重断言
type Result<T> =
| { status: 'success'; data: T }
| { status: 'error'; message: string }
| { status: 'loading' }
function handleResult<T>(result: Result<T>): string {
switch (result.status) {
case 'success':
return `Data: ${result.data}`
case 'error':
return `Error: ${result.message}`
case 'loading':
return 'Loading...'
}
}
// Good: 自定义守卫提高代码可读性
interface User {
id: number
name: string
email: string
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj
)
}
function handleResponse(data: unknown): void {
if (isUser(data)) {
console.log(data.name) // 类型安全
}
}
// Good: 必要时的类型断言(框架/库边界)
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d')!
// Good: 使用 satisfies 操作符(TS 4.9+)
type Palette = Record<string, string>
const palette = {
red: '#FF0000',
green: '#00FF00',
blue: '#0000FF',
} satisfies Palette
// palette.red 的类型是 string(满足约束),而非 string | undefinedas 断言的使用原则
- 尽可能不用 —— 优先类型守卫
- 仅在边界使用 —— DOM API、JSON.parse、第三方库
- 配合验证 —— 断言前进行运行时检查
- 宁可用 unknown 过渡 ——
as unknown as T比直接as T更显眼
// 可接受的使用场景
const data = JSON.parse(jsonString) as User[] // 外部边界
// 更好的做法
function parseUsers(json: string): User[] {
const data = JSON.parse(json)
if (!Array.isArray(data) || !data.every(isUser)) {
throw new Error('Invalid user data')
}
return data
}code-tabs 对比
总结
| Go 概念 | TypeScript 对应 |
|---|---|
x.(Type) 安全断言 (v, ok) | 类型守卫 typeof/instanceof/is |
x.(Type) 不安全断言 | as Type 断言 |
switch v := x.(type) | 可辨识联合 + switch kind |
| 无对应 | 自定义守卫 pet is Fish |
| 无对应 | 非空断言 ! |
| 无对应 | satisfies 操作符 |
核心原则:优先类型守卫(编译时收窄),次选类型断言(告知编译器),类型断言越少,代码越安全。
下一章将介绍 高级类型:联合、交叉与映射类型。