使用用户自定义类型守卫简化 TypeScript 代码
2023年8月31日

在使用 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 提供了强大的功能,用于创建健壮、可维护的代码。然而,冗长有时会成为一个问题。用户自定义类型守卫通过提供一种更优雅、干净的方式来执行类型检查,从而解决了这个问题,使代码更干净、更易读,也更不容易出错。

分类

TypeScript