理解 TypeScript 的 Never 类型

开发 前端
对于很多人来说,TypeScript 中的 never 类型比较神秘,它有什么作用?什么时候应该使用它?

今天我们就来深入讨论 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 代码的好工具。


责任编辑:武晓燕 来源: 前端充电宝
相关推荐

2022-11-30 07:17:53

2024-11-05 09:11:09

TypeScript开发者代码

2021-07-27 06:06:34

TypeScript语言运算符

2024-09-30 08:34:01

TypeScript可读性安全性

2024-11-11 08:32:00

2021-08-18 07:56:05

Typescript类型本质

2024-05-11 10:19:31

TypeScript类型接口

2022-04-11 08:42:09

TypeScript子类型定义

2022-08-08 09:00:42

TypeScript映射类型

2021-09-09 07:21:26

TypeScript 高级类型

2023-03-28 09:56:47

TypeScripJavaScrip

2022-04-18 20:12:03

TypeScript静态类型JavaScrip

2024-06-13 09:05:12

2022-09-20 14:43:55

TypeScript类型体操

2025-01-20 00:13:19

TypeScript操作符数据类型

2021-06-09 07:55:19

Typescript类型检查

2022-03-24 09:44:54

TypeScriptSOLID

2022-05-04 09:02:41

TypeScript类型工具

2020-09-15 08:35:57

TypeScript JavaScript类型

2022-08-10 09:03:35

TypeScript前端
点赞
收藏

51CTO技术栈公众号