类型守卫(Type Guard)是 TypeScript 中用于在运行时检查类型的表达式,可以帮助编译器在特定代码块中确定变量的具体类型。
typeof 类型守卫#
使用 typeof 检查原始类型:
// TypeScript 5.x
function process(value: string | number) { if (typeof value === 'string') { // 这里 value 被收窄为 string console.log(value.toUpperCase()) console.log(value.length) } else { // 这里 value 被收窄为 number console.log(value.toFixed(2)) console.log(value * 2) }}
process('hello') // HELLO, 5process(3.14159) // 3.14, 6.28318
// typeof 支持的类型function checkType(value: unknown) { if (typeof value === 'string') { return 'string' } if (typeof value === 'number') { return 'number' } if (typeof value === 'boolean') { return 'boolean' } if (typeof value === 'function') { return 'function' } if (typeof value === 'object') { return 'object or null' } if (typeof value === 'undefined') { return 'undefined' } if (typeof value === 'symbol') { return 'symbol' } if (typeof value === 'bigint') { return 'bigint' }}instanceof 类型守卫#
使用 instanceof 检查类实例:
class Dog { bark() { console.log('汪汪!') }}
class Cat { meow() { console.log('喵喵!') }}
function makeSound(animal: Dog | Cat) { if (animal instanceof Dog) { animal.bark() // animal 是 Dog } else { animal.meow() // animal 是 Cat }}
const dog = new Dog()const cat = new Cat()
makeSound(dog) // 汪汪!makeSound(cat) // 喵喵!
// 处理内置类型function processValue(value: Date | string[]) { if (value instanceof Date) { console.log(value.toISOString()) } else { console.log(value.join(', ')) }}
processValue(new Date()) // 2024-02-05T...processValue(['a', 'b', 'c']) // a, b, c
// 处理错误function handleError(error: unknown) { if (error instanceof Error) { console.log(error.message) console.log(error.stack) } else if (typeof error === 'string') { console.log(error) } else { console.log('未知错误') }}in 操作符类型守卫#
使用 in 检查属性是否存在:
interface Bird { fly(): void layEggs(): void}
interface Fish { swim(): void layEggs(): void}
function move(animal: Bird | Fish) { if ('fly' in animal) { animal.fly() // animal 是 Bird } else { animal.swim() // animal 是 Fish }}
// 常用于可辨识联合interface Circle { kind: 'circle' radius: number}
interface Rectangle { kind: 'rectangle' width: number height: number}
type Shape = Circle | Rectangle
function getArea(shape: Shape): number { if ('radius' in shape) { return Math.PI * shape.radius ** 2 } else { return shape.width * shape.height }}
// 检查可选属性interface User { name: string email?: string phone?: string}
function contact(user: User) { if ('email' in user && user.email) { console.log(`发送邮件到: ${user.email}`) } else if ('phone' in user && user.phone) { console.log(`拨打电话: ${user.phone}`) } else { console.log('没有联系方式') }}自定义类型守卫#
使用 is 关键字创建自定义类型守卫:
// 基本语法function isString(value: unknown): value is string { return typeof value === 'string'}
function isNumber(value: unknown): value is number { return typeof value === 'number'}
function process(value: unknown) { if (isString(value)) { console.log(value.toUpperCase()) // value 是 string } else if (isNumber(value)) { console.log(value.toFixed(2)) // value 是 number }}
// 检查对象类型interface User { id: number name: string email: string}
function isUser(value: unknown): value is User { return ( typeof value === 'object' && value !== null && 'id' in value && 'name' in value && 'email' in value && typeof (value as User).id === 'number' && typeof (value as User).name === 'string' && typeof (value as User).email === 'string' )}
function greet(data: unknown) { if (isUser(data)) { console.log(`Hello, ${data.name}!`) // 类型安全 } else { console.log('Invalid user data') }}
// 检查数组function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every((item) => typeof item === 'string')}
function processArray(data: unknown) { if (isStringArray(data)) { data.forEach((s) => console.log(s.toUpperCase())) }}断言函数#
使用 asserts 关键字创建断言函数:
// 断言函数:如果检查失败则抛出错误function assertIsString(value: unknown): asserts value is string { if (typeof value !== 'string') { throw new Error('Value is not a string') }}
function process(value: unknown) { assertIsString(value) // 之后 value 被视为 string console.log(value.toUpperCase())}
// 断言非空function assertDefined<T>(value: T | null | undefined): asserts value is T { if (value === null || value === undefined) { throw new Error('Value is null or undefined') }}
function getLength(str: string | null) { assertDefined(str) return str.length // str 是 string}
// 断言条件function assert(condition: boolean, message: string): asserts condition { if (!condition) { throw new Error(message) }}
function divide(a: number, b: number) { assert(b !== 0, 'Divisor cannot be zero') return a / b}类型守卫与泛型#
// 泛型类型守卫function isDefined<T>(value: T | null | undefined): value is T { return value !== null && value !== undefined}
const values: (string | null | undefined)[] = ['a', null, 'b', undefined, 'c']const defined = values.filter(isDefined)// defined: string[]
// 带约束的泛型守卫interface WithId { id: string}
function hasId<T>(value: T): value is T & WithId { return typeof value === 'object' && value !== null && 'id' in value}
function processItem<T>(item: T) { if (hasId(item)) { console.log(`ID: ${item.id}`) }}
// 类型谓词与映射function isNotNull<T>(value: T | null): value is T { return value !== null}
const items: (number | null)[] = [1, null, 2, null, 3]const numbers = items.filter(isNotNull)// numbers: number[]实际应用#
API 响应验证#
interface SuccessResponse<T> { success: true data: T}
interface ErrorResponse { success: false error: string}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse
function isSuccess<T>( response: ApiResponse<T>): response is SuccessResponse<T> { return response.success === true}
interface User { id: number name: string}
async function fetchUser(id: number): Promise<User | null> { const response: ApiResponse<User> = await fetch(`/api/users/${id}`).then( (r) => r.json() )
if (isSuccess(response)) { return response.data // 类型安全 } else { console.error(response.error) return null }}表单数据验证#
interface FormData { username: string email: string age: number}
interface ValidationResult { valid: boolean errors: string[]}
function isValidFormData(data: unknown): data is FormData { if (typeof data !== 'object' || data === null) { return false }
const obj = data as Record<string, unknown>
return ( typeof obj.username === 'string' && obj.username.length >= 3 && typeof obj.email === 'string' && obj.email.includes('@') && typeof obj.age === 'number' && obj.age >= 0 && obj.age <= 150 )}
function processForm(data: unknown): ValidationResult { if (isValidFormData(data)) { // 安全处理表单数据 console.log(`用户: ${data.username}, 邮箱: ${data.email}`) return { valid: true, errors: [] } } return { valid: false, errors: ['Invalid form data'] }}事件处理#
interface MouseEvent { type: 'click' | 'mousemove' x: number y: number}
interface KeyboardEvent { type: 'keydown' | 'keyup' key: string code: string}
type AppEvent = MouseEvent | KeyboardEvent
function isMouseEvent(event: AppEvent): event is MouseEvent { return event.type === 'click' || event.type === 'mousemove'}
function isKeyboardEvent(event: AppEvent): event is KeyboardEvent { return event.type === 'keydown' || event.type === 'keyup'}
function handleEvent(event: AppEvent) { if (isMouseEvent(event)) { console.log(`鼠标位置: (${event.x}, ${event.y})`) } else if (isKeyboardEvent(event)) { console.log(`按键: ${event.key}`) }}常见问题#
🙋 typeof null 返回什么?#
// typeof null 返回 'object',这是 JavaScript 的历史遗留问题function isObject(value: unknown): value is object { return typeof value === 'object' && value !== null}
console.log(typeof null) // 'object'console.log(isObject(null)) // falseconsole.log(isObject({})) // true🙋 instanceof 对原始类型有效吗?#
// instanceof 只对对象有效const str = 'hello'console.log(str instanceof String) // false
const strObj = new String('hello')console.log(strObj instanceof String) // true
// 使用 typeof 检查原始类型console.log(typeof str === 'string') // true🙋 如何组合多个类型守卫?#
function isStringOrNumber(value: unknown): value is string | number { return typeof value === 'string' || typeof value === 'number'}
function isArrayOfStringsOrNumbers( value: unknown): value is (string | number)[] { return Array.isArray(value) && value.every(isStringOrNumber)}总结#
| 类型守卫 | 语法 | 适用场景 |
|---|---|---|
| typeof | typeof x === 'type' | 原始类型检查 |
| instanceof | x instanceof Class | 类实例检查 |
| in | 'prop' in obj | 属性存在检查 |
| 自定义守卫 | value is Type | 复杂类型检查 |
| 断言函数 | asserts value is Type | 验证并抛出错误 |
下一篇我们将深入学习类型收窄,了解 TypeScript 如何通过控制流分析收窄类型。