原文参考地址: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
另外一种情形是,没有break
或return
语句的无限循环:
const output = () => { while (true) { console.log("This will get annoying quickly"); } } // TypeScript 推断函数返回值类型是 never
上面两个情形中,TypeScript 都会把函数的返回值推断为never
类型。
never
和void
的区别
那么,void
类型不是和never
很像吗?在我们已经有了void
类型的时候,为什么要需要再加一个never
类型呢?
never
和void
最主要的区别在于,void
类型可以把undefined
或null
作为其合法值。
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 代码。