首页 TypeScript TypeScript never 类型

TypeScript never 类型

0 1.7K

原文参考地址:https://itnext.io/typescript-basics-understanding-the-never-type-3b4bdaa7859c

TypeScript 的never类型对很多开发者都有点神秘。它究竟是干什么用的,什么时候应该使用它?现在,我们就要谈论一下never关键字,以及什么情况应该使用。

never的特性

TypeScript 使用never关键字表示逻辑上不会发生的情形或者控制流。

在现实情况中,你一般不会经常使用到never关键字,但了解一下它是如何保证 TypeScript 的类型安全总是有好处的。

首先,我们来看一下文档中是如何描述它的:

The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or assignable to, never (except never itself).

根据文档的说法,never是任意类型的子类型,并且可以赋值给任意类型。但是,没有任何类型是never类型的子类型,任何其它类型也不能赋值给never(除了never自己)。

下面看一个例子:

const throwErrorFunc = () => { throw new Error("LOL") };

let neverVar: never = throwErrorFunc();
const myString = '';
const myInt: number = neverVar;
neverVar = myString; // Type 'string' is not assignable to type 'never';

目前为止可以忽略掉throwErrorFunc。这是初始化never类型的一种变通方法。

上面的代码显示了,我们可以把never类型的neverVar变量赋值给number类型的myInt。但是,不能把string类型的myString赋值给neverVar,否则 TypeScript 会报错。

你不能把任何其它类型的变量赋值给never,甚至是any类型。

函数中的never

TypeScript 使用never表示一种函数的返回值,这种函数的return语句永远不会执行到。这种函数通常有两种类型:

  • 函数抛出异常
  • 函数有无限循环

我们之前已经见到了抛出异常的函数,也就是上面的throwErrorFunc

const throwErrorFunc = () => {
  throw new Error("LOL")
}; // TypeScript 推断函数返回值类型是 never

另外一种情形是,没有breakreturn语句的无限循环:

const output = () => {
  while (true) {
    console.log("This will get annoying quickly");
  }
} // TypeScript 推断函数返回值类型是 never

上面两个情形中,TypeScript 都会把函数的返回值推断为never类型。

nevervoid的区别

那么,void类型不是和never很像吗?在我们已经有了void类型的时候,为什么要需要再加一个never类型呢?

nevervoid最主要的区别在于,void类型可以把undefinednull作为其合法值。

TypeScript 使用void表示函数不会返回任何值。当我们不显式指定函数的返回值时,并且这个函数的任意路径都不会返回任意值,那么 TypeScript 就会推断这个函数的返回值类型是void

在 TypeScript 中,看起来返回void的函数没有返回任何值,但实际函数返回了undefined

const functionWithVoidReturnType = () => {}; // TypeScript 推断函数返回值类型是 void
console.log(functionWithVoidReturnType()); // undefined

只不过,我们通常直接忽略了void类型的返回值。

另外需要注意的是,按照之前我们介绍过的never类型的特性,void类型是不可以赋值给never类型的:

const myVoidFunction = () => {}
neverVar = myVoidFunction() // 错误:never 不能赋值给 void

变量守卫never

如果一个变量类型被类型守卫缩窄到永远不会为达到,那么这个类型就成了never。通常,这意味着你的类型守卫的判断逻辑有问题。

例如,

const unExpectedResult = (myParam: "this" | "that") => {
    if (myParam === "this") {
    } else if (myParam === "that") {
    } else {
      console.log({ myParam })
    }
  }

上面的代码中,只要执行到console.log({ myParam })一行时,myParams的类型就会是never

这是因为,我们设置myParam的类型要么是"this",要么是"that"。既然 TypeScript 已经假设myParam只可能是这两种类型,逻辑上,第三个else分支永远不应该执行到,所以 TypeScript 将其类型推断为never

穷举检查

你可能遇到never类型的情形之一是穷举检查。穷举检查可以让你的代码覆盖到每一种可能的情况。

使用穷举检查,我们可以实现一种更好的switch语句:

type Animal = "cat" | "dog" | "bird";

const shouldNotHappen = (animal: never) => {
    throw new Error("Didn't expect to get here")
  };

const myPicker = (pet: Animal) => {
    switch(pet) {
      case "cat": {
        // handle cat
        return;
      }
      case "dog": {
        // handle dog
        return;
      }
    }
    return shouldNotHappen(pet);
  }

当你添加了return shouldNotHappen(pet);语句,你立刻可以看到一个错误:

这个错误暗示了你的switch分支没有包含所有情况。这种错误在编译期就可以检查到,强制要求你的switch语句覆盖所有情形。

结论

正如上面所说的那样,never类型在某些特殊的情况下是很有用的。大多数时候,出现了never意味着你的代码可能有隐藏的缺陷。但在某些情形,比如穷举检查的时候,never类型可以帮助你写出更安全的 TypeScript 代码。

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2