1. 类型守卫与类型谓词的本质区别
在TypeScript的类型系统中,类型守卫(Type Guard)和类型谓词(Type Predicate)经常被混淆使用,但它们有着本质的区别。类型守卫是通过运行时检查来缩小类型范围的表达式,而类型谓词则是开发者自定义的类型判断函数。
1.1 类型守卫的常见形式
TypeScript内置了几种基础的类型守卫形式:
// typeof 类型守卫 function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value } return padding + value } // instanceof 类型守卫 class Bird { fly() { console.log("flying") } } class Fish { swim() { console.log("swimming") } } function move(pet: Bird | Fish) { if (pet instanceof Bird) { pet.fly() } else { pet.swim() } } // in 操作符守卫 interface Admin { name: string privileges: string[] } interface Employee { name: string startDate: Date } function printInfo(emp: Admin | Employee) { if ("privileges" in emp) { console.log("Privileges: " + emp.privileges) } }1.2 类型谓词的特殊语法
类型谓词使用特殊的返回值类型标注,格式为parameterName is Type:
function isString(test: any): test is string { return typeof test === "string" } function example(foo: any) { if (isString(foo)) { // 这里foo被推断为string类型 console.log(foo.length) } }类型谓词函数必须返回boolean值,TypeScript会根据返回值true/false来调整参数的类型。这种机制使得我们可以创建复杂的自定义类型判断逻辑。
重要提示:类型谓词函数应该始终是纯函数,相同的输入必须产生相同的输出结果,否则会导致类型系统的不一致。
2. 联合类型的处理模式
联合类型(Union Types)是TypeScript中表示"或"关系的类型,处理联合类型时需要考虑所有可能的类型分支。
2.1 联合类型的判别式属性
对于对象类型的联合,使用判别式属性(Discriminant Property)是最佳实践:
interface Square { kind: "square" size: number } interface Rectangle { kind: "rectangle" width: number height: number } interface Circle { kind: "circle" radius: number } type Shape = Square | Rectangle | Circle function area(shape: Shape): number { switch (shape.kind) { case "square": return shape.size * shape.size case "rectangle": return shape.width * shape.height case "circle": return Math.PI * shape.radius ** 2 } }这种模式被称为"可辨识联合"(Discriminated Unions),通过公共的kind属性来区分不同类型。
2.2 穷尽性检查
TypeScript提供了never类型来帮助进行穷尽性检查:
function assertNever(x: never): never { throw new Error("Unexpected object: " + x) } function areaWithExhaustiveCheck(shape: Shape): number { switch (shape.kind) { case "square": return shape.size * shape.size case "rectangle": return shape.width * shape.height case "circle": return Math.PI * shape.radius ** 2 default: return assertNever(shape) // 如果有未处理的case会报错 } }这种技巧在新增联合类型成员时特别有用,TypeScript会提示你需要更新所有相关的switch语句。
3. 高级类型谓词模式
3.1 泛型类型谓词
类型谓词可以与泛型结合,创建更灵活的类型判断:
function isArrayOf<T>( arr: any[], check: (x: any) => x is T ): arr is T[] { return arr.every(check) } function isNumber(x: any): x is number { return typeof x === "number" } const testArray: any[] = [1, 2, 3] if (isArrayOf(testArray, isNumber)) { // testArray被推断为number[] const sum = testArray.reduce((a, b) => a + b, 0) }3.2 异步类型谓词
在异步场景下,我们可以定义返回Promise的类型谓词:
async function isUserAuthenticated( request: Request ): Promise<request is AuthenticatedRequest> { const token = request.headers.get("Authorization") if (!token) return false return await verifyToken(token) } interface AuthenticatedRequest extends Request { user: { id: string name: string } }3.3 类型谓词与类型映射
结合类型映射,可以创建更复杂的类型判断:
type NonNullableProperty<T, K extends keyof T> = { [P in K]: NonNullable<T[P]> } function hasNonNullProperties<T, K extends keyof T>( obj: T, keys: K[] ): obj is T & NonNullableProperty<T, K> { return keys.every(key => obj[key] != null) } interface User { name?: string email?: string age?: number } function processUser(user: User) { if (hasNonNullProperties(user, ["name", "email"])) { // 这里user.name和user.email被推断为非null console.log(`Sending email to ${user.name} at ${user.email}`) } }4. 实际应用场景与性能考量
4.1 API响应验证
类型谓词非常适合用于API响应验证:
interface ApiResponse<T> { success: boolean data?: T error?: string } function isSuccessResponse<T>( response: ApiResponse<T>, dataCheck?: (data: any) => data is T ): response is { success: true; data: T } { if (!response.success) return false if (dataCheck) return dataCheck(response.data) return response.data !== undefined } interface UserData { id: string name: string } function isUserData(data: any): data is UserData { return typeof data?.id === "string" && typeof data?.name === "string" } async function fetchUser(userId: string) { const response = await fetch(`/api/users/${userId}`) const result: ApiResponse<UserData> = await response.json() if (isSuccessResponse(result, isUserData)) { return result.data // 类型安全地访问data } throw new Error(result.error || "Unknown error") }4.2 性能优化建议
虽然类型谓词非常强大,但需要注意:
- 复杂的类型谓词函数会增加运行时开销,特别是在热路径中
- 对于简单的类型判断,优先使用内置的类型守卫
- 避免在类型谓词中进行IO操作或其他副作用
- 考虑将复杂的类型谓词结果缓存起来
// 不推荐的写法 - 每次调用都创建新正则 function isEmail(x: any): x is string { return typeof x === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(x) } // 改进写法 - 复用正则对象 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ function isEmailOptimized(x: any): x is string { return typeof x === "string" && emailRegex.test(x) }4.3 与TypeScript新特性的结合
随着TypeScript版本更新,类型谓词可以与其他新特性结合使用:
// 使用模板字面量类型 type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" function isHttpMethod(x: string): x is HttpMethod { return ["GET", "POST", "PUT", "DELETE"].includes(x) } // 使用satisfies操作符(TS 4.9+) const ROLES = { ADMIN: "admin", USER: "user" } as const function isRole(x: string): x is keyof typeof ROLES { return x in ROLES }类型谓词是TypeScript类型系统中一个强大但常被低估的特性。通过合理使用,可以显著提高代码的类型安全性,同时保持运行时的灵活性。关键在于找到类型严格性和开发便利性之间的平衡点。