likes
comments
collection
share

慎用 TypeScript 感叹号『非空断言』操作符

作者站长头像
站长
· 阅读数 15

TS 中的感叹号,称作『非空断言』操作符,Non-null assertion operator

中文理解:

  • x! 将从 x 值对应的类型集合中排除 nullundefined 的类型。比如 x 可能是 string | undefind,则 x! 类型缩窄为 string
  • 在类型检测器没法正确推断类型情况下,告知编译器此值不可能为空值(nullundefined

何时会用到

什么时候会用到『非空断言』,或者说怎么去了解上面这两句话呢?我们举个例子即一目了然。

function isValid(x: any): boolean {
  return x !== undefined && x !== null;
}

function test(y?: string): string {
  if (isValid(y)) {
    return y.toLowerCase(); // 在 isValid 分支内,y 明明不可能是 undefined,为何还报错『Object is possibly 'undefined'.(2532)』?🤔
  }

  return '';
}

报错信息如下:

慎用 TypeScript 感叹号『非空断言』操作符

isValid 分支内,y 明明不可能是 undefined,为何还报错『Object is possibly 'undefined'.(2532)』?🤔

原因是 TS 并不具备运行时检测,或者仅具备有限的类型接检测能力(文章尾部有利用 TS 静态检查能力的版本,推荐使用,本示例只是演示!的作用),如果 isValid 改成 inline 的则 TS 能知道 y 一定不为空,则不会出现 TS error 了。

function test(y?: string): string {
  if (y !== undefined && y !== null) {
    return y.toLowerCase(); // 类型检查其能正确推断此时 y 不可能为空
  }

  return '';
}

若我们的校验逻辑很复杂,或者我们就是想用现成的函数,怎么办?其实有更好的办法能两全其美,可以通过非空断言主动告知迟钝的 TS 类型检查器该变量不可能为空。

当然我们要为自己的行为负责,如果欺骗 TS 导致 NPE(Null Pointer Exception)那就不是 TS 的锅了。

function test(y?: string): string {
  if (isValid(y)) {
    return y!.toLowerCase(); // y 经过函数校验后不可能是 undefined,我们可通过非空断言告知 TS
  }

  return '';
}

! 可用来帮助 TS 类型检测器做类型判断。但须慎用。

附录:无需 ! 利用 TS 类型检查的版本

function isValid<T>(x: T): x is NotNil<T> {
  return x !== undefined && x !== null;
}

function test(y?: string): string {
  if (isValid(y)) {
    return y.toLowerCase(); // 此时 y 不会提示报错。因为被 isValid 断言非空
  }

  return '';
}

type NotNil<T> = T extends null | undefined ? never : T

此处巧妙利用了自定义的 NotNil 泛型。其实 TS 内置了相同功能的类型 NonNullable。 更多泛型工具可以参考类型界的 lodash type-fest

学会自定义泛型,同理可以自定义 IDefined

type IDefined<T> = T extends undefined ? never : T

参考