TypeScript 类型保护和类型谓词
联合类型使我们能够接受多种不同类型的参数。提供 typex
或y
。有时,这些类型并非 100% 兼容。它们服务于相同的目标,但具有不同的属性。在后续阶段,我们可能希望基于确切的类型运行一些代码。这时类型保护和类型谓词就派上用场了。
入门
我们先声明一些类型。我喜欢在解释某些东西的时候看代码。这能让我更好地理解概念。
假设我们正在建立一个博客,并且有两种类型,它们形成一个联合。
type Article = {
frontMatter: Record<string, unknown>;
content: string;
}
type NotFound = {
notFound: true;
}
type Page = Article | NotFound;
具体类型是Article
和NotFound
,而 是Page
并集。目标是编写一个函数来渲染页面。我不会详细讨论检查博客是否存在以及何时调用该notFound
函数的具体要求,但假设我们只有一个渲染函数。根据数据库中的内容,我们渲染文章或未找到的页面。例如:
function handleRequest(slug: string): string {
const article = db.articles.findOne({ slug });
const page = article ?? { notFound: true };
return render(page);
}
我们面临的挑战是,当我们需要知道handleRequest
传递给 的是Article
还是NotFound
类型 时render
。在 JavaScript 中,你可以使用类似如下的代码:
function render(page: Page) {
if (page.content) {
return page.content;
}
return '404 — not found';
}
但在 TypeScript 中,这是行不通的。它会抛出一个错误,提示该属性content
在 type 上不存在Page
。
Property 'content' does not exist on type 'Page'.
Property 'content' does not exist on type 'NotFound'.
这是因为联合体中的并非所有类型都包含该属性。为了解决这个问题,我们需要添加类型保护。
类型保护
类型保护是一种执行运行时检查的表达式,用于保证当前范围内的类型。
page.content
快速修复方法是用 TypeScript 可以理解的东西替换该检查:
function render(page: Page) {
if ('content' in page) {
return page.content;
}
return '404 — not found';
}
这确实可行,但确实会降低可维护性。TypeScript 的优点是,当我们删除正在使用的属性时,它会发出警告。进行此更改后,当我们将属性重命名content
为body
例如 时,或者当我们在 中输入错误时,TypeScript 不会发出警告'content'
。
这就是类型谓词有趣的原因。
类型谓词
类型谓词是这样的函数的返回类型:
function isArticle(page: Page): page is Article {
return 'content' in page;
}
谓词并非指整个函数。谓词是page is Article
。另外需要注意的是,'content' in page
在这种情况下, 不是类型保护。它是一个简单的表达式。类型保护是if
导致 TypeScript 缩小类型的语句。
因此,上面的函数看起来与之前的类型保护非常相似,并且存在同样的可维护性问题。但是,既然我们已经将其提取出来,我们也可以解决这个问题。
function isArticle(page: Page): page is Article {
return typeof (page as Article).content !== 'undefined';
}
这有效,但当我们重构Article
并删除该content
属性时会出现错误。
声明为类型谓词的函数必须返回布尔值。当返回值为 时true
,TypeScript 会假定返回类型是类型谓词中声明的类型。如果此函数返回 true,则 TypeScript 会假定提供的参数page
为 类型Article
。
当我们在渲染函数中调用此方法时:
function render(page: Page) {
if (isArticle(page)) {
return page.content;
}
return '404 — not found';
}
TypeScript 知道page.content
存在,因为在 if 作用域内,page
是 类型Article
。if (isArticle(page))
表达式 是一个类型保护。
在 if 语句之后,page
不是 类型Article
。而且由于我们的联合只有 2 种类型,因此 TypeScript 也知道它NotFound
此时必须是 类型。
👋 我是 Stephan,正在开发rake.red。如果你想阅读更多我的文章,请在Twitter上关注我。
文章来源:https://dev.to/smeijer/typescript-type-guards-and-type-predicates-4m5e