TypeScript 3.7 在类型系统中实现了对断言函数的支持。断言函数是一个在发生意外情况下抛出错误的函数。使用断言签名,我们可以告诉TypeScript,一个函数应该被视为断言函数。
一个例子
document.getElementById()
方法
让我们先看一个例子,我们使用
document.getElementById()
方法来寻找一个ID为 "root "的DOM元素。
const root = document.getElementById("root");
root.addEventListener("click", e => {
/* ... */
我们正在调用root.addEventListener()
方法来给这个元素附加一个点击处理程序。然而,TypeScript报告了一个类型错误。
const root = document.getElementById("root");
// Object is possibly null
root.addEventListener("click", e => {
/* ... */
root
变量的类型是HTMLElement | null
,这就是为什么当我们试图调用root.addEventListener()
方法时,TypeScript报告类型错误 "Object is possibly null"。为了使我们的代码被认为是类型正确的,我们需要在调用root.addEventListener()
方法之前,确保root
变量是非空的和非未定义的。对于如何做到这一点,我们有几个选择,包括。
使用非空的断言操作符!
实现一个内联的空值检查
实现一个断言函数
让我们来看看这三个选项中的每一个。
使用非空值断言运算符
首先,我们将尝试使用非空断言操作符!
,它被写成了document.getElementById()
调用后的修正操作符。
const root = document.getElementById("root")!;
root.addEventListener("click", e => {
/* ... */
非空断言操作符!
告诉 TypeScript 假设由document.getElementById()
返回的值是非空和非未定义的(也被称为 "非空")。TypeScript将从我们应用!
操作符的表达式的类型中排除null
和undefined
的类型。
在这种情况下,document.getElementById()
方法的返回类型是HTMLElement | null
,所以如果我们应用!
操作符,我们得到HTMLElement
作为结果类型。因此,TypeScript不再报告我们之前看到的类型错误。
然而,在这种情况下,使用非空断言操作符可能不是正确的修复方法。当我们的TypeScript代码被编译为JavaScript时,!
操作符被完全删除。
const root = document.getElementById("root");
root.addEventListener("click", e => {
/* ... */
非空断言操作符没有任何运行时的表现形式。也就是说,TypeScript编译器不会发出任何验证代码来验证表达式是否真的是非空的。因此,如果document.getElementById()
调用返回null
,因为找不到匹配的元素,我们的root
变量将持有值null
,我们试图调用root.addEventListener()
方法将失败。
实施一个内联空值检查
现在让我们考虑第二个选项,实现一个内联空值检查,以验证root
变量持有一个非空值。
const root = document.getElementById("root");
if (root === null) {
throw Error("Unable to find DOM element #root");
root.addEventListener("click", e => {
/* ... */
由于我们的空值检查,TypeScript的类型检查器会将root
变量的类型从HTMLElement | null
(空值检查前)缩小到HTMLElement
(空值检查后)。
const root = document.getElementById("root");
// Type: HTMLElement | null
root;
if (root === null) {
throw Error("Unable to find DOM element #root");
// Type: HTMLElement
root;
root.addEventListener("click", e => {
/* ... */
这种方法比之前使用非空断言操作符的方法要安全得多。我们通过抛出一个带有描述性错误信息的错误来明确地处理root
变量持有值null
的情况。
此外,请注意,这种方法不包含任何TypeScript特定的语法;上述所有内容都是语法上有效的JavaScript。TypeScript的控制流分析理解我们的空值检查的效果,并在程序的不同地方缩小了root
变量的类型--不需要明确的类型注释。
实现一个断言函数
最后,让我们看看如何使用断言函数以可重复使用的方式来实现这个空值检查。我们将首先实现一个assertNonNullish
函数,如果提供的值是null
或undefined
,将抛出一个错误。
function assertNonNullish(
value: unknown,
message: string
if (value === null || value === undefined) {
throw Error(message);
我们在这里为value
参数使用unknown
类型,以允许调用者传递一个任意类型的值。我们只是将value
参数与null
和undefined
的值进行比较,所以我们不需要要求value
参数有一个更具体的类型。
下面是我们在之前的例子中如何使用assertNonNullish
函数。我们把root
变量以及错误信息传递给它。
const root = document.getElementById("root");
assertNonNullish(root, "Unable to find DOM element #root");
root.addEventListener("click", e => {
/* ... */
然而,TypeScript仍然对root.addEventListener()
方法的调用产生了一个类型错误。
const root = document.getElementById("root");
assertNonNullish(root, "Unable to find DOM element #root");
// Object is possibly null
root.addEventListener("click", e => {
/* ... */
如果我们在调用assertNonNullish()
之前和之后看一下root
变量的类型,我们会发现它在两个地方都是HTMLElement | null
的类型。
const root = document.getElementById("root");
// Type: HTMLElement | null
root;
assertNonNullish(root, "Unable to find DOM element #root");
// Type: HTMLElement | null
root;
root.addEventListener("click", e => {
/* ... */
这是因为TypeScript不明白,如果提供的value
是空的,我们的assertNonNullish
函数将抛出一个错误。我们需要明确地让TypeScript知道,assertNonNullish
函数应该被视为一个断言函数,断言该值是非空的,否则它将抛出一个错误。我们可以使用返回类型注释中的asserts
关键字来做到这一点。
function assertNonNullish<TValue>(
value: TValue,
message: string
): asserts value is NonNullable<TValue> {
if (value === null || value === undefined) {
throw Error(message);
首先,请注意,assertNonNullish
函数现在是一个通用函数。它声明了一个单一类型的参数TValue
,我们将其作为value
参数的类型;我们也在返回类型注释中使用TValue
的类型。
asserts value is NonNullable<TValue>
返回类型注解就是所谓的断言签名。这个断言签名说,如果函数正常返回(也就是说,如果它没有抛出一个错误),它已经断言value
参数的类型是NonNullable<TValue>
。TypeScript使用这条信息来缩小我们传递给value
参数的表达式的类型。
NonNullable<T>
类型是一个条件类型,它被定义在lib.es5.d.ts类型声明文件中,该文件随TypeScript编译器一起提供。
* Exclude null and undefined from T
type NonNullable<T> = T extends null | undefined ? never : T;
T
当应用于类型T
,NonNullable<T>
帮助类型从null
和undefined
。下面是几个例子。
NonNullable<HTMLElement>
评价为HTMLElement
NonNullable<HTMLElement | null>
评价为HTMLElement
NonNullable<HTMLElement | null | undefined>
评价为HTMLElement
NonNullable<null>
评价为never
NonNullable<undefined>
评价为never
NonNullable<null | undefined>
评价为never
有了我们的断言签名,TypeScript现在正确地缩小了assertNonNullish()
函数调用后的root
变量的类型。类型检查器理解,当root
持有一个空值时,assertNonNullish
函数将抛出一个错误。如果程序的控制流通过了assertNonNullish()
函数调用,root
变量必须包含一个非空值,因此 TypeScript 相应地缩小了其类型。
const root = document.getElementById("root");
// Type: HTMLElement | null
root;
assertNonNullish(root, "Unable to find DOM element #root");
// Type: HTMLElement
root;
root.addEventListener("click", e => {
/* ... */
作为这种类型缩小的结果,我们的例子现在可以正确地进行类型检查。
const root = document.getElementById("root");
assertNonNullish(root, "Unable to find DOM element #root");
root.addEventListener("click", e => {
/* ... */
所以我们有了:一个可重用的assertNonNullish
断言函数,我们可以用它来验证一个表达式是否有一个非空值,并且通过移除null
和undefined
的类型来相应地缩小这个表达式的类型。