在使用 TypeScript 进行开发时,精确的类型检查是常态,这有时需要重复的类型断言来告诉编译器我们对变量类型的“了解”。尽管这种强大的类型系统提高了代码质量,但有时也会变得很冗长,尤其是在处理 DOM 元素时。本博客文章旨在介绍 TypeScript 中的一项功能,即用户自定义类型守卫,它能减少代码冗余,使您的 TypeScript 代码更加易读和可维护。
问题描述
假设我们有一个 div
元素,如果它的第一个子元素也是一个具有某些属性和类的 div
,我们想对其执行几个操作。
let divElement!: HTMLDivElement;
if (
divElement.firstChild &&
divElement.firstChild.nodeType === Node.ELEMENT_NODE &&
(divElement.firstChild as HTMLElement).tagName === 'DIV' &&
(divElement.firstChild as HTMLElement).hasAttribute('data-item-id') &&
(divElement.firstChild as HTMLElement).classList.contains('item')
) {
(divElement.firstChild as HTMLElement).click();
}
代码检查了 divElement.firstChild
是否为 HTMLElement
,然后进行了其他检查。然而,由于我们重复使用 as HTMLElement
来进行类型断言,这种方式变得相当冗长。
用户自定义类型守卫介绍
TypeScript 允许我们创建自己的类型守卫,这些类型守卫是用来执行运行时检查的函数,并使用特殊的返回类型来告知 TypeScript 在特定的上下文中关于类型的信息。这个功能被称为用户自定义类型守卫。
创建类型守卫函数
以下是如何创建一个类型守卫函数,用于检查一个节点是否为 HTMLElement
。
function isHTMLElement(node: Node): node is HTMLElement {
return node.nodeType === Node.ELEMENT_NODE;
}
这里的 node is HTMLElement
是类型谓词。它告诉 TypeScript 编译器,当这个函数返回 true
时,作为 node
传入的变量应被视为一个 HTMLElement
。
使用类型守卫
有了类型守卫函数,我们就可以如下重构原来的代码:
let divElement!: HTMLDivElement;
if (
isHTMLElement(divElement.firstChild) &&
divElement.firstChild.tagName === 'DIV' &&
divElement.firstChild.hasAttribute('data-item-id') &&
divElement.firstChild.classList.contains('item')
) {
divElement.firstChild.click();
}
优点
减少冗余:通过用户自定义类型守卫,我们减少了重复类型断言的需求。
提高可读性:因为代码少了类型断言,所以更容易阅读和理解。
可重用代码:
isHTMLElement
函数可以在应用的多个地方重用,使您的代码库保持 DRY(Don't Repeat Yourself)原则。
总结
TypeScript 提供了强大的功能,用于创建健壮、可维护的代码。然而,冗长有时会成为一个问题。用户自定义类型守卫通过提供一种更优雅、干净的方式来执行类型检查,从而解决了这个问题,使代码更干净、更易读,也更不容易出错。