今天我们就来深入讨论 never 类型,并介绍可能遇到的情况。
1. never 的特点
TypeScript使用never关键字来表示逻辑上不应该发生的情况和控制流。实际上,我们在工作中不会常遇到使用 never 的情况,但是还是很有必要了解它是如何有助于 TypeScript 的类型安全的。
官方文档对 never 的描述:
never 类型是任何类型的子类型,也可以赋值给任何类型;但是,没有类型是never的子类型或可以赋值给never类型(never 本身除外)。
也就是说,可以将never类型的变量分配给任何其他变量,但不能将其他变量分配给never。下面来看一个例子:
const throwErrorFunc = () => { throw new Error("error") };
let neverVar: never = throwErrorFunc()
const myString = ""
const myInt:number = neverVar;
neverVar = myString // Type 'string' is not assignable to type 'never'
- 1.
- 2.
- 3.
- 4.
- 5.
我们可以暂时忽略 throwErrorFunc 的功能,只需知道,这样可以初始化类型为 never 的变量。
从上面的代码中可以看到,可以将 never 类型的变量 neverVar 分配给 number 类型的变量myInt。但是,不能将 string 类型的 myString 变量分配给 neverVar,这样分配会报错。这也就是上面所说的,不能将任何其他类型的变量分配给 never,即使是 any 类型的变量。
2. 函数中的 never
TypeScript 使用 never 作为那些无法达到的终点的函数的返回值类型。主要有两种情况:
函数抛出一个错误异常。
函数包含一个无限循环。
来看上面提到的 throwErrorFunc 函数,TypeScript 就会推断此函数的返回类型为 never:
const throwErrorFunc = () => {
throw new Error("error")
};
- 1.
- 2.
- 3.
另一种情况就是如果有一个一直为真的表达式产生了无限循环,它没有中断或者返回语句。TypeScript 就会推断此函数的返回类型为 never:
const output = () => {
while (true) {
console.log("循环");
}
};
- 1.
- 2.
- 3.
- 4.
- 5.
3. never 和 void 的区别
那什么是 void 类型呢?我们有 void 为什么还要 never 类型呢?
never 和 void 的主要区别在于,void 类型的值可以是 undefined 或 null。
TypeScript 对不返回任何内容的函数使用 void。如果没有为函数指定返回类型,并且在代码中没有返回任何内容,TypeScript 将推断其返回类型为void。在TypeScript中,不返回任何内容的 void 函数实际上返回的是 undefined。
来看一个例子:
const functionWithVoidReturnType = () => {};
console.log(functionWithVoidReturnType()); // undefined
- 1.
- 2.
这里,TypeScript 会推断此函数的返回类型为 void。我们通常会忽略 void 函数的返回值。
这里需要注意:根据 never 类型的特征,我们不能将 void 指定给 never:
const myVoidFunction = () => {}
neverVar = myVoidFunction() // ERROR: Type 'never' is not assignable to type 'void'
- 1.
- 2.
4. never 作为可变类型守
如果变量被一个永远不可能为 true 的类型保护缩小范围,那么变量就可能成为 never类型。通常,这表明条件逻辑存在缺陷。
来看下面的例子:
const unExpectedResult = (myParam: "this" | "that") => {
if (myParam === "this") {
} else if (myParam === "that") {
} else {
console.log({ myParam })
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
在这个例子中,当函数执行到 console.log({ myParam }) 时,myParam 的类型将为 never。
这是因为我们将 myParam 的类型设置为了 this 或 that。由于 TypeScript 认为 myParam 类型属于两个类型之一,所以从逻辑上讲,第三个 else 语句永远不会出现。所以TypeScript 就会将参数类型设置为 never。
5. 详尽检查
在实践中可能会用到 never 的一个地方就是进行详细的检查。这有助于确保我们处理了代码中的每个边缘情况。
下面来看看如何使用详尽检查为 switch 语句添加更好的类型安全性:
type Animal = "cat" | "dog" | "bird"
const shouldNotHappen = (animal: never) => {
throw new Error("error")
}
const myPicker = (pet: Animal) => {
switch(pet) {
case "cat": {
// ...
return
}
case "dog": {
// ...
return
}
}
return shouldNotHappen(pet)
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
当添加 「return shouldNotHappen(pet)」时,会看到一个错误提示:
这里的错误提示告诉我们,忘记包含在 switch 语句中的情况。这是一种获得编译时安全性并确保处理 switch 语句中的所有情况的巧妙模式。
6. 结论如你所见,TypeScript 中的 never 类型在特定情况很有用。大多数情况下,never 表明代码存在缺陷。
但在某些情况下,例如详尽检查,它可以成为帮助编写更安全的 TypeScript 代码的好工具。