编程前端开发者

TypeScript 如何处理用户定义的类型保护(自定义类型保护函数),它们的目的是什么,以及如何创建正确的类型谓词函数?在实现过程中会遇到哪些典型陷阱?

用 Hintsage AI 助手通过面试

答案。

背景:TypeScript 扩展了常规的类型检查,允许开发人员创建自己的用户定义功能——类型保护——来检查对象是否符合特定类型。这对于处理联合类型、动态结构和可能具有不同类型值的 API 是必要的。

问题通常在于,通过 typeof 和 instanceof 进行的常规定义类型检查仅限于原始类型和类,而对于结构或复杂类型则无法进行定义。必须能够明确地向编译器提示在何种情况下值安全地缩小到所需类型。

解决方案是编写类型保护函数,例如 function isCat(obj: Animal): obj is Cat {...},关键点在于函数返回类型中的类型谓词。

代码示例:

interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }

关键特性:

  • 类型检查通过特定的类型谓词函数实现,形式为 param is Type。
  • 可以创建自己的保护函数,并将其用于任意结构,而不仅限于类对象。
  • 不正确的类型保护可能导致运行时错误和类型推断错误。

陷阱问题。

在类型保护函数中返回 true/false 是否足以让编译器缩小类型?

不。重要的是明确指定返回类型为类型谓词(例如,pet is Dog),否则即使函数只返回 true 或 false,TypeScript 也不会自动缩小值的类型。

可以在回调中使用类型保护(例如在 filter 中),那么缩小是否能够正常工作?

可以,如果类型保护正确注释,则编译器将在 filter 之后以及 forEach/callback 函数内部缩小数组元素的类型。但是,如果没有注释或写错,结果将具有联合类型,而不是具体类型。

const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript 知道,dogs: Dog[]

用户定义的类型保护与通过 typeof、instanceof 进行的常规类型检查有什么区别?

类型保护函数可以实现任何结构的检查,描述任何复杂性级别的检查,操作接口而非仅限于基本类型和类。

常见错误和反模式

  • 类型保护不返回类型谓词类型,而只是布尔值——对编译器没有效果。
  • 只检查表面的键,未能保证结构完整性。
  • 类型保护始终返回 true,实际上禁用类型保护。
  • 输入参数过于一般,或者在没有验证的情况下进行隐式类型转换。

生活中的示例

负面案例

函数在没有类型谓词的情况下过滤用户:

function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]

优点:

  • 快速,效果在运行时显著

缺点:

  • 过滤后,TypeScript 中的数据类型没有明确,访问属性时可能会出错。

正面案例

函数使用正确的类型谓词进行过滤:

interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]

优点:

  • 结果的类型明确
  • 减少了在后续处理结果时出错的可能性

缺点:

  • 需要更多关注参数签名和覆盖检查