TypeScript 类型保护和类型谓词

2025-06-07

TypeScript 类型保护和类型谓词

联合类型使我们能够接受多种不同类型的参数。提供 typexy。有时,这些类型并非 100% 兼容。它们服务于相同的目标,但具有不同的属性。在后续阶段,我们可能希望基于确切的类型运行一些代码。这时类型保护和类型谓词就派上用场了。

入门

我们先声明一些类型。我喜欢在解释某些东西的时候看代码。这能让我更好地理解概念。

假设我们正在建立一个博客,并且有两种类型,它们形成一个联合。

type Article = {
  frontMatter: Record<string, unknown>;
  content: string;
}

type NotFound = { 
  notFound: true;
}

type Page = Article | NotFound;
Enter fullscreen mode Exit fullscreen mode

具体类型是ArticleNotFound,而 是Page并集。目标是编写一个函数来渲染页面。我不会详细讨论检查博客是否存在以及何时调用该notFound函数的具体要求,但假设我们只有一个渲染函数。根据数据库中的内容,我们渲染文章或未找到的页面。例如:

function handleRequest(slug: string): string {
  const article = db.articles.findOne({ slug });
  const page = article ?? { notFound: true };
  return render(page);
}
Enter fullscreen mode Exit fullscreen mode

我们面临的挑战是,当我们需要知道handleRequest传递给 的是Article还是NotFound类型 时render。在 JavaScript 中,你可以使用类似如下的代码:

function render(page: Page) {
  if (page.content) {
    return page.content;
  }

  return '404 — not found';
}
Enter fullscreen mode Exit fullscreen mode

但在 TypeScript 中,这是行不通的。它会抛出一个错误,提示该属性content在 type 上不存在Page

Property 'content' does not exist on type 'Page'.
  Property 'content' does not exist on type 'NotFound'.
Enter fullscreen mode Exit fullscreen mode

这是因为联合体中的并非所有类型都包含该属性。为了解决这个问题,我们需要添加类型保护。

类型保护

类型保护是一种执行运行时检查的表达式,用于保证当前范围内的类型。

page.content快速修复方法是用 TypeScript 可以理解的东西替换该检查:

function render(page: Page) {
  if ('content' in page) {
    return page.content;
  }

  return '404 — not found';
}
Enter fullscreen mode Exit fullscreen mode

这确实可行,但确实会降低可维护性。TypeScript 的优点是,当我们删除正在使用的属性时,它会发出警告。进行此更改后,当我们将属性重命名contentbody例如 时,或者当我们在 中输入错误时,TypeScript 不会发出警告'content'

这就是类型谓词有趣的原因。

类型谓词

类型谓词是这样的函数的返回类型:

function isArticle(page: Page): page is Article {
  return 'content' in page;
}
Enter fullscreen mode Exit fullscreen mode

谓词并非指整个函数。谓词是page is Article。另外需要注意的是,'content' in page在这种情况下, 不是类型保护。它是一个简单的表达式。类型保护是if导致 TypeScript 缩小类型的语句。

因此,上面的函数看起来与之前的类型保护非常相似,并且存在同样的可维护性问题。但是,既然我们已经将其提取出来,我们也可以解决这个问题。

function isArticle(page: Page): page is Article {
  return typeof (page as Article).content !== 'undefined';
}
Enter fullscreen mode Exit fullscreen mode

这有效,但当我们重构Article并删除该content属性时会出现错误。

声明为类型谓词的函数必须返回布尔值。当返回值为 时true,TypeScript 会假定返回类型是类型谓词中声明的类型。如果此函数返回 true,则 TypeScript 会假定提供的参数page为 类型Article

当我们在渲染函数中调用此方法时:

function render(page: Page) {
  if (isArticle(page)) {
    return page.content;
  }

  return '404 — not found';
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 知道page.content存在,因为在 if 作用域内,page是 类型Articleif (isArticle(page))表达式 是一个类型保护。

在 if 语句之后,page不是 类型Article。而且由于我们的联合只有 2 种类型,因此 TypeScript 也知道它NotFound此时必须是 类型。


👋 我是 Stephan,正在开发rake.red。如果你想阅读更多我的文章,请在Twitter上关注我。

文章来源:https://dev.to/smeijer/typescript-type-guards-and-type-predicates-4m5e
PREV
反应合成事件
NEXT
Typescript 类型断言