TypeScript 类型守卫详解
一、什么是类型守卫?
类型守卫(Type Guard)是 TypeScript 中用于 缩小变量类型范围 的机制。通过特定的条件判断,TypeScript 编译器能够 智能推断出更精确的类型,从而增强类型安全性。
二、核心类型守卫类型
1. typeof
类型守卫
function padLeft(value: string | number, padding: string | number) {if (typeof padding === "number") { // 类型守卫return " ".repeat(padding) + value // 这里 padding 被识别为 number}return padding + value // 这里 padding 被识别为 string
}
支持类型:string
、number
、bigint
、boolean
、symbol
、undefined
、object
、function
注意:typeof null
返回 "object"
,需配合其他守卫使用
2. instanceof
类型守卫
class FileError extends Error {constructor(public path: string, message: string) {super(message)}
}function handleError(err: Error | FileError) {if (err instanceof FileError) { // 类型守卫console.log(`文件错误路径: ${err.path}`)} else {console.log(err.message)}
}
3. in
操作符守卫
interface Dog {bark(): void
}interface Cat {meow(): void
}function animalSound(animal: Dog | Cat) {if ('bark' in animal) { // 类型守卫animal.bark()} else {animal.meow()}
}
4. 自定义类型谓词(User-Defined Type Guards)
// 类型谓词语法:parameterName is Type
function isString(value: unknown): value is string {return typeof value === 'string'
}function process(input: string | number) {if (isString(input)) { // 自定义类型守卫console.log(input.toUpperCase())} else {console.log(input.toFixed(2))}
}
5. 可辨识联合类型(Discriminated Unions)
interface Circle {kind: "circle" // 判别式radius: number
}interface Square {kind: "square" // 判别式sideLength: number
}type Shape = Circle | Squarefunction getArea(shape: Shape) {switch (shape.kind) { // 类型守卫case "circle":return Math.PI * shape.radius ** 2 // 识别为 Circlecase "square":return shape.sideLength ** 2 // 识别为 Square}
}
三、进阶守卫技巧
1. 联合类型过滤
type Primitive = string | number | boolean | null | undefinedfunction isPrimitive(value: unknown): value is Primitive {return (typeof value === 'string' ||typeof value === 'number' ||typeof value === 'boolean' ||value === null ||value === undefined)
}
2. 类型守卫组合
function isErrorLike(value: unknown): value is Error {return value instanceof Error || (typeof value === 'object' && value !== null && 'message' in value && typeof (value as any).message === 'string')
}
3. 泛型类型守卫
function isArrayOf<T>(arr: unknown,check: (item: unknown) => item is T
): arr is T[] {return Array.isArray(arr) && arr.every(check)
}// 使用示例
const data: unknown = [1, 2, 3]
if (isArrayOf(data, (item): item is number => typeof item === 'number')) {const sum = data.reduce((a, b) => a + b, 0) // data 被识别为 number[]
}
四、常见应用场景
1. API 响应处理
interface SuccessResponse<T> {success: truedata: T
}interface ErrorResponse {success: falseerror: string
}type ApiResponse<T> = SuccessResponse<T> | ErrorResponsefunction handleResponse<T>(response: ApiResponse<T>) {if (response.success) {console.log(response.data) // 识别为 SuccessResponse<T>} else {console.error(response.error) // 识别为 ErrorResponse}
}
2. 表单验证
type FormField = string | number | File | nullfunction validateFile(field: FormField): field is File {return field instanceof File && field.size > 0
}function uploadFile(field: FormField) {if (!validateFile(field)) {throw new Error('无效的文件')}// 此处 field 被识别为 Fileapi.upload(field)
}
五、类型守卫 vs 类型断言
特性 | 类型守卫 | 类型断言 |
---|---|---|
工作机制 | 通过逻辑判断缩小类型范围 | 强制指定类型 |
类型安全性 | 高(编译器验证) | 低(开发者负责) |
适用场景 | 需要动态判断类型的场景 | 明确知道类型但编译器无法推断 |
代码示例 | if (isString(value)) { ... } | const str = value as string |
六、最佳实践指南
- 优先使用内置守卫:
typeof
、instanceof
、in
等原生操作符 - 复杂逻辑使用自定义守卫:超过 2 个条件判断时建议提取为独立函数
- 避免过度使用
any
:类型守卫应替代大多数as any
场景 - 联合类型设计:优先使用可辨识联合类型(Discriminated Unions)
- 守卫函数命名规范:使用
isXxx
前缀(如isAdminUser
)
七、调试技巧
// 使用 never 类型检测穷尽性检查
function assertNever(x: never): never {throw new Error("Unexpected object: " + x)
}function handleShape(shape: Shape) {switch (shape.kind) {case "circle":// ...case "square":// ...default:assertNever(shape) // 如果新增类型会报错}
}
掌握类型守卫后,可显著提升以下场景的开发效率:
- 处理 API 响应数据
- 表单验证逻辑
- 第三方库类型适配
- 复杂状态管理
推荐练习:尝试将项目中所有 as any
的类型断言替换为类型守卫。