前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TS 如何进行完整性检查

TS 如何进行完整性检查

作者头像
阿宝哥
发布2020-03-27 10:17:10
1.9K0
发布2020-03-27 10:17:10
举报
文章被收录于专栏:全栈修仙之路全栈修仙之路

一、never 类型

在 TypeScript 中,never 类型表示的是那些永不存在的值的类型。 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。此外,变量也可能是 never 类型,当它们被永不为真的类型保护所约束时。为了让大家更好的理解 never 类型,我们来举一些实际的例子。

在定义变量时,可以设置变量的类型为 never 类型:

代码语言:javascript
复制
let foo: never; // 定义never类型的变量

never 类型是任何类型的子类型,也可以赋值给任何类型:

代码语言:javascript
复制
let bar: string = (() => {
  throw new Error('TypeScript never');
})();

然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never

代码语言:javascript
复制
let baz: never = 123; // 赋值失败,number类型不能赋值给never类型的变量

// 定义never类型变量,接收返回值类型为never类型的函数返回值
let bar: never = (() => {
  throw new Error('TypeScript never');
})();

另外,对于死循环的函数或执行时总会抛出异常的函数来说,函数对应的返回值类型也是 never 类型,比如:

代码语言:javascript
复制
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
  return error("Some error happened");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
  while (true) {}
}

相信对于刚接触 never 类型的大多数读者来说,看到这里时,心中都会有疑惑 —— never 类型到底有什么用?在 TypeScript 中,可以利用 never 类型的特性来实现完整性检查。

二、利用异常机制实现完整性检查

考虑以下枚举:

代码语言:javascript
复制
enum NoYes {
  No = 'No',
  Yes = 'Yes',
}

下面我们可以在 switch 语句中来使用 NoYes 枚举:

代码语言:javascript
复制
function toChinese(x: NoYes) {
  switch (x) {
    case NoYes.No:
      return '否';
    case NoYes.Yes:
      return '是';
    default:
      throw new UnsupportedValueError(x); // (A)
  }
}

在 A 行中,参数 x 的类型被推断为 never 类型,因为我们已经处理了它可能含有的所有值。因此,我们可以在 A 行中实例化以下异常:

代码语言:javascript
复制
class UnsupportedValueError extends Error {
  constructor(value: never) {
    super('Unsupported value: ' + value);
  }
}

但是,如果我们忘记了其中一个条件分支的话,那么参数 x 的类型就不再是 never 类型了,我们得到了一个静态的错误:

代码语言:javascript
复制
function toChinese(x: NoYes) {
  switch (x) {
    case NoYes.Yes:
      return '是';
    default:
      // Argument of type 'NoYes.No' is not assignable to parameter of type 'never'.
      throw new UnsupportedValueError(x); // Error
  }
}

以上的报错信息很明显,因为我们只处理了 NoYes.Yes 的情形,TypeScript 编译器会推断出 default 分支中变量 x 的类型是 NoYes.No 类型,根据前面介绍的 never 类型的知识,我们知道它是不能赋给 never 类型的变量。

如果你想忽略上述错误,则可以使用 // @ts-ignore 来忽略错误:

代码语言:javascript
复制
function toChinese(x: NoYes) {
  switch (x) {
    case NoYes.Yes:
      return '是';
    default:
      //@ts-ignore: Argument of type 'NoYes.No' is not assignable to parameter of type 'never'. (2345)
      throw new UnsupportedValueError(x); // Error
  }
}

TypeScript 2.6 支持在 .ts 文件中通过在报错一行上方使用 // @ts-ignore 来忽略错误。 // @ts-ignore 注释会忽略下一行中产生的所有错误。 建议实践中在 @ts-ignore之后添加相关提示,解释忽略了什么错误。 请注意,这个注释仅会隐藏报错,并且我们建议你少使用这一注释。

1.1 好处:也适用于 if 语句

如果我们使用 if 语句,TypeScript 也会警告我们:

代码语言:javascript
复制
function toChineseNonExhaustively(x: NoYes) {
  if (x === NoYes.Yes) {
    return '是';
  } else {
    // @ts-ignore: Argument of type 'NoYes.No' is not assignable to parameter of type 'never'. (2345)
    throw new UnsupportedValueError(x);
  }
}

function toChineseExhaustively(x: NoYes) {
  if (x === NoYes.No) {
    return '否';
  } else if (x === NoYes.Yes) {
    return '是';
  } else {
    throw new UnsupportedValueError(x); // Ok
  }
}

好了,接下来我们来介绍进行完整性检查的另一种方法。

三、利用返回类型实现完整性检查

除了利用异常机制之外,我们还可以利用返回类型校验,来实现完整性检查。如果我们忘记处理某个条件分支,TypeScript 也会警告我们(因为我们隐式返回 undefined):

代码语言:javascript
复制
enum NoYes {
  No = 'No',
  Yes = 'Yes',
}

//@ts-ignore: Function lacks ending return statement and return type does not include 'undefined'. (2366)
function toChinese(x: NoYes): string {
  switch (x) {
    case NoYes.Yes:
      return '是';
  }
}

以上错误信息的意思是:函数缺少结尾的 return 语句,并且返回类型不包含 undefined 类型。

2.1 缺点:不适用于 if 语句

使用这种方法,即使我们完整地处理了所有情况,我们也还会收到警告:

代码语言:javascript
复制
enum NoYes {
  No = 'No',
  Yes = 'Yes',
}

// @ts-ignore: Function lacks ending return statement and return type does not include 'undefined'. (2366)
function toChineseExhaustive(x: NoYes): string {
  if (x === NoYes.Yes) {
    return '是';
  }
}

// @ts-ignore: Function lacks ending return statement and return type does not include 'undefined'. (2366)
function toChineseExhaustive(x: NoYes): string {
  if (x === NoYes.No) {
    return '否';
  } else if (x === NoYes.Yes) {
    return '是';
  }
}

对于代码中的 toChineseExhaustive 方法来说,如果我们把函数方法体中的 if 语句换成 switch 语句的话,是不会收到任何警告的:

代码语言:javascript
复制
function toChineseExhaustive(x: NoYes): string {
  switch (x) {
    case NoYes.Yes:
      return '是';
    case NoYes.No:
      return '否'
  }
}
2.2 比较这两种方法

与使用异常机制相比,该方法有何不同?

  • 好处:实现起来简单
  • 缺点:
    • 运行时无保护,即不会抛出任何异常
    • 不适用于 if 语句

四、总结

本文介绍了 TypeScript 中实现完整性检查的两种方法并通过实际的例子来介绍它们之间的差异。在例子中虽然我们只使用了枚举类型作为演示,但这种模式也适用于其它类型,比如联合类型和可辨识联合。

五、参考资源

  • typescript-exhaustiveness-checks-via-exceptions
  • typescript-2.6
  • tslang-advanced-types
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020/03/26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、never 类型
    • 二、利用异常机制实现完整性检查
      • 1.1 好处:也适用于 if 语句
    • 三、利用返回类型实现完整性检查
      • 2.1 缺点:不适用于 if 语句
      • 2.2 比较这两种方法
    • 四、总结
      • 五、参考资源
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档