Haoyuan's blog Haoyuan's blog
首页
导航站
  • Java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • Spring Cloud
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
留言区
GitHub (opens new window)

Somnus Haoyuan

Word is cheap, show me the code.
首页
导航站
  • Java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • Spring Cloud
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
留言区
GitHub (opens new window)
  • 超文本标记语言 - Html

  • 解释编程语言 - JavaScript

  • JS 超集语言 - TypeScript

    • TypeScript - 介绍
    • TypeScript - 安装和使用
    • TypeScript - 基本类型
    • TypeScript - 编译和配置
    • TypeScript - 文件打包
    • TypeScript - 接口
    • TypeScript - 函数
    • TypeScript - 类
    • TypeScript - 泛型
    • TypeScript - 类型推断
    • TypeScript - 高级类型
    • TypeScript Office - 入门
    • TypeScript Office - 常用类型
    • TypeScript Office - 类型缩小
      • 类型缩小
      • typeof 类型守卫
      • 真值缩小
      • 等值缩小
      • in 操作符缩小
      • instanceof 操作符缩小
      • 分配缩小
      • 控制流分析
      • 使用类型谓词
      • 受歧视的 unions
      • never 类型与穷尽性检查
    • TypeScript Office - 更多函数
    • TypeScript Office - 对象类型
    • TypeScript Office - 类型操纵
    • TypeScript Office - 类
    • TypeScript Office - 模块
    • TypeScript Office - 变量声明
    • TypeScript Office - 类型推断
    • TypeScript Office - 枚举
    • TypeScript Office - 公共类型
    • TypeScript Office - Symbols
    • TypeScript Office - 类型兼容性
    • TypeScript Office - 迭代器和生成器
    • TypeScript Office - 装饰器
    • TypeScript Office - JSX
    • TypeScript Office - 混入
    • TypeScript Office - 三斜线指令
    • TypeScript Office - 模块进阶
    • TypeScript Office - 模块解析
    • TypeScript Office - 命名空间
    • TypeScript Office - 命名空间与模块
    • TypeScript Office - 声明合并
  • 界面构建框架 - React

  • 渐进式框架 - Vue2

  • 渐进式框架 - Vue3

  • 前端
  • JS 超集语言 - TypeScript
Haoyuan
2022-09-12
目录

TypeScript Office - 类型缩小

  • 类型缩小
  • typeof 类型守卫
  • 真值缩小
  • 等值缩小
  • in 操作符缩小
  • instanceof 操作符缩小
  • 分配缩小
  • 控制流分析
  • 使用类型谓词
  • 受歧视的 unions
  • never 类型与穷尽性检查

# 类型缩小

假设我们有一个名为 padLeft 的函数:

function padLeft(padding: number | string, input: string): string {
    throw new Error("尚未实现!");
}
1
2
3

我们来扩充一下功能:如果 padding 是 number,它会将其视为我们想要添加到 input 的空格数;如果 padding 是 string,它只在 input 上做 padding。让我们尝试实现:

function padLeft(padding: number | string, input: string) {
    return new Array(padding + 1).join(" ") + input;
}
1
2
3

呃-哦,在 padding + 1 处我们遇到错误。TypeScript 警告我们,运算符 + 不能应用于类型 string | number 和 number,这是对的。换句话说,我们没有明确检查 padding 是否为 number,也没有处理它是 string 的情况,所以我们这样做:

function padLeft(padding: number | string, input: string) {
    if (typeof padding === "number") {
        return new Array(padding + 1).join(" ") + input;
    }
    return padding + input;
}
1
2
3
4
5
6

如果这大部分看起来像无趣的 JavaScript 代码,这也算是重点吧。除了我们设置的注解之外,这段 TypeScript 代码看起来就像 JavaScript。我们的想法是,TypeScript 的类型系统旨在使编写典型的 JavaScript 代码变得尽可能容易,而不需要弯腰去获得类型安全。

虽然看起来不多,但实际上有很多东西在这里。就像 TypeScript 使用静态类型分析运行时的值一样,它在 JavaScript 的运行时控制流构造上叠加了类型分析,如 if/else、条件三元组、循环、真实性检查等,这些都会影响到这些类型。

在我们的 if 检查中,TypeScript 看到 typeof padding ==="number",并将其理解为一种特殊形式的代码,称为类型保护。TypeScript 遵循我们的程序可能采取的执行路径,以分析一个值在特定位置的最具体的可能类型。它查看这些特殊的检查(称为类型防护)和赋值,将类型细化为比声明的更具体的类型的过程被称为缩小。在许多编辑器中,我们可以观察这些类型的变化,我们甚至会在我们的例子中这样做。

TypeScript 可以理解几种不同的缩小结构。

# typeof 类型守卫

正如我们所见,JavaScript 支持一个 typeof 运算符,它可以提供有关我们在运行时拥有的值类型的非常基本的信息。TypeScript 期望它返回一组特定的字符串:

  • "string"
  • "number"
  • "bigint"
  • "boolean"
  • "symbol"
  • "undefined"
  • "object"
  • "function"

就像我们在 padLeft 中看到的那样,这个运算符经常出现在许多 JavaScript 库中,TypeScript 可以理解为,它缩小在不同分支中的类型。

在 TypeScript 中,检查 typeof 的返回值是一种类型保护。因为 TypeScript 对 typeof 操作进行编码,从而返回不同的值,所以它知道对 JavaScript 做了什么。例如,请注意在上面的列表中,typeof 不返回 string null。查看以下示例:

function printAll(strs: string | string[] | null) {
    if (typeof strs === "object") {
        for (const s of strs) {
            console.log(s);
        }
    } else if (typeof strs === "string") {
        console.log(strs);
    } else {
        // 做点事
    }
}
1
2
3
4
5
6
7
8
9
10
11

在 printAll 函数中,我们尝试检查 strs 是否为对象,来代替检查它是否为数组类型(现在可能是强调数组是 JavaScript 中的对象类型的好时机)。但事实证明,在 JavaScript 中,typeof null 实际上也是 "object",这是历史上的不幸事故之一。

有足够经验的用户可能不会感到惊讶,但并不是每个人都在 JavaScript 中遇到过这种情况;幸运的是,typescript 让我们知道,strs 只缩小到 string[] | null,而不仅仅是 string[] 。

这可能是我们所谓的「真实性」检查的一个很好的过渡。

# 真值缩小

真值检查是我们在 JavaScript 中经常做的一件事。在 JavaScript 中,我们可以在条件、&&、||、if 语句、布尔否定 (!) 等中使用任何表达式。例如,if 语句不希望它们的条件总是具有类型 boolean。

function getUsersOnlineMessage(numUsersOnline: number) {
    if (numUsersOnline) {
        return `现在共有 ${numUsersOnline} 人在线!`;
    }
    return "现在没有人在线. :(";
}
1
2
3
4
5
6

在 JavaScript 中,像这样的 if 条件语句,首先将它们的条件「强制」转化为 boolean 以使其有意义,然后根据结果是 true 还是 false 来选择它们的分支。像这面这些值:

  • 0
  • NaN
  • ""(空字符串)
  • 0n(bigint 零的版本)
  • null
  • undefined

以上所有值强制都转换为 false,其他值被强制转化为 true。你始终可以在 Boolean 函数中运行值获得 boolean,或使用较短的双布尔否定将值强制转换为 boolean。(后者的优点是 TypeScript 推断出一个狭窄的文字布尔类型 true,而将第一个推断为 boolean 类型)

// 这两个结果都返回 true
Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true, value: true
1
2
3

利用这种行为是相当流行的,尤其是在防范诸如 null 或 undefined 之类的值时。例如,让我们尝试将它用于我们的 printAll 函数。

function printAll(strs: string | string[] | null) {
    if (strs && typeof strs === "object") {
        for (const s of strs) {
            console.log(s);
        }
    } else if (typeof strs === "string") {
        console.log(strs);
    }
}
1
2
3
4
5
6
7
8
9

你会注意到我们已经通过检查 strs 是否为真,消除了上述错误。这至少可以防止我们在运行代码时出现可怕的错误,例如:

TypeError: null is not iterable
1

但请记住,对原语的真值检查通常容易出错。例如,考虑改写 printAll:

function printAll(strs: string | string[] | null) {
    // !!!!!!!!!!!!!!!!
    // 别这样!
    // 原因在下边
    // !!!!!!!!!!!!!!!!
    if (strs) {
        if (typeof strs === "object") {
            for (const s of strs) {
                console.log(s);
            }
        } else if (typeof strs === "string") {
            console.log(strs);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

我们将整个函数体包装在一个真实的检查中,但这有一个小的缺点:我们可能不再正确处理空字符串的情况。

TypeScript 在这里根本不会报错,但是如果你不太熟悉 JavaScript,这是值得注意的行为。TypeScript 通常可以帮助你及早发现错误,但是如果你选择对某个值不做任何处理,那么它可以做的就只有这么多,而不会考虑过多的逻辑方面的问题。如果需要,你可以确保使用 linter(程序规范性) 处理此类情况。

关于通过真实性缩小范围的最后一点,是通过布尔否定 ! 把逻辑从否定分支中过滤掉。

function multiplyAll(
values: number[] | undefined,
 factor: number
): number[] | undefined {
    if (!values) {
        return values;
    } else {
        return values.map((x) => x * factor);
    }
}
1
2
3
4
5
6
7
8
9
10

# 等值缩小

TypeScript 也使用分支语句做 ===,!==,== 和 != 等值检查,来实现类型缩小。例如:

function example(x: string | number, y: string | boolean) {
    if (x === y) {
        // 现在可以在x,y上调用字符串类型的方法了
        x.toUpperCase();
        y.toLowerCase();
    } else {
        console.log(x);
        console.log(y);
    }
}
1
2
3
4
5
6
7
8
9
10

当我们在上面的示例中检查 x 和 y 是否相等时,TypeScript 知道它们的类型也必须相等。由于 string 是 x 和 y 都可以采用的唯一常见类型,因此TypeScript 知道 x、y 如果都是 string,则程序走第一个分支中。

检查特定的字面量值(而不是变量)也有效。在我们关于真值缩小的部分中,我们编写了一个 printAll 容易出错的函数,因为它没有正确处理空字符串。相反,我们可以做一个特定的检查来阻止 null,并且 TypeScript 仍然正确地从 strs 里移除 null。

function printAll(strs: string | string[] | null) {
    if (strs !== null) {
        if (typeof strs === "object") {
            for (const s of strs) {
                console.log(s);
            }
        } else if (typeof strs === "string") {
            console.log(strs);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

JavaScript 更宽松的相等性检查 == 和 != 也能被正确缩小。如果你不熟悉,如何检查某个变量是否 == null,因为有时不仅要检查它是否是特定的值 null,还要检查它是否可能是 undefined。这同样适用于 == undefined:它检查一个值是否为 null 或 undefined。现在你只需要这个 == 和 != 就可以搞定了。

interface Container {
    value: number | null | undefined;
}
function multiplyValue(container: Container, factor: number) {
    // 从类型中排除了undefined 和 null
    if (container.value != null) {
        console.log(container.value);
        // 现在我们可以安全地乘以「container.value」了
        container.value *= factor;
    }
}
1
2
3
4
5
6
7
8
9
10
11

# in 操作符缩小

JavaScript 有一个运算符,用于确定对象是否具有某个名称的属性:in 运算符。TypeScript 考虑到了这一点,以此来缩小潜在类型的范围。

例如,使用代码:"value" in x。这里的 "value" 是字符串文字,x 是联合类型。值为 true 的分支缩小,需要 x 具有可选或必需属性的类型的值;值为 false 的分支缩小,需要具有可选或缺失属性的类型的值。

type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
    if ("swim" in animal) {
        return animal.swim();
    }
    return animal.fly();
}
1
2
3
4
5
6
7
8

另外,可选属性还将存在于缩小的两侧,例如,人类可以游泳和飞行(使用正确的设备),因此应该出现在 in 检查的两侧:

type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void };
function move(animal: Fish | Bird | Human) {
    if ("swim" in animal) {
        // animal: Fish | Human
        animal;
    } else {
        // animal: Bird | Human
        animal;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# instanceof 操作符缩小

JavaScript 有一个运算符来 instanceof 检查一个值是否是另一个值的 实例。更具体地,在 JavaScript 中 x instanceof Foo 检查 x 的原型链是否含有 Foo.prototype。虽然我们不会在这里深入探讨,当我们进入类(class) 学习时,你会看到更多这样的内容,它们大多数可以使用 new 关键字实例化。正 如你可能已经猜到的那样,instanceof 也是一个类型保护,TypeScript 在由 instanceof 保护的分支中实现缩小。

function logValue(x: Date | string) {
    if (x instanceof Date) {
        console.log(x.toUTCString());
    } else {
        console.log(x.toUpperCase());
    }
}
logValue(new Date()) // Mon, 15 Nov 2021 22:34:37 GMT
logValue('hello ts') // HELLO TS
1
2
3
4
5
6
7
8
9

# 分配缩小

正如我们之前提到的,当我们为任何变量赋值时,TypeScript 会查看赋值的右侧并适当缩小左侧。

// let x: string | number
let x = Math.random() < 0.5 ? 10 : "hello world!";
x = 1;
// let x: number
console.log(x);
x = "goodbye!";
// let x: string
console.log(x);
1
2
3
4
5
6
7
8

请注意,这些分配中的每一个都是有效的。即使在我们第一次赋值后观察到的类型 x 更改为 number,我们仍然可以将 string 赋值给 x。这是因为声明类型的 x,该类型 x 开始是 string | number。

如果我们分配了一个 boolean 给 x,我们就会看到一个错误,因为它不是声明类型的一部分。

let x = Math.random() < 0.5 ? 10 : "hello world!";
// let x: string | number
x = 1;
// let x: number
console.log(x);
// 出错了!
x = true;
// let x: string | number
console.log(x);
1
2
3
4
5
6
7
8
9

# 控制流分析

到目前为止,我们已经通过一些基本示例来说明 TypeScript 如何在特定分支中缩小范围。但是除了从每个变量中走出来,并在 if、while、条件等中寻找类型保护之外,还有更多的事情要做。例如:

function padLeft(padding: number | string, input: string) {
    if (typeof padding === "number") {
        return new Array(padding + 1).join(" ") + input;
    }
    return padding + input;
}
1
2
3
4
5
6

padLeft 从其第一个 if 块中返回。TypeScript 能够分析这段代码,并看到在 padding 是数字的情况下,主体的其余部分( return padding + input; )是不可达的。因此,它能够将数字从 padding 的类型中移除(从字符串|数字缩小到字符串),用于该函数的其余部分。

这种基于可达性的代码分析被称为控制流分析,TypeScript 使用这种流分析来缩小类型,因为它遇到了类型守卫和赋值。当一个变量被分析时,控制流可以一次又一次地分裂和重新合并,该变量可以被观察到在每个点上有不同的类型。

function example() {
    let x: string | number | boolean;
    x = Math.random() < 0.5;
    // let x: boolean
    console.log(x);
    if (Math.random() < 0.5) {
        x = "hello";
        // let x: string
        console.log(x);
    } else {
        x = 100;
        // let x: number
        console.log(x);
    }
    // let x: string | number
    return x;
}
let x = example()
x = 'hello'
x = 100
x = true // error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 使用类型谓词

到目前为止,我们已经用现有的 JavaScript 结构来处理窄化问题,然而有时你想更直接地控制整个代码中的类型变化。

为了定义一个用户定义的类型保护,我们只需要定义一个函数,其返回类型是一个类型谓词。

type Fish = {
    name: string
    swim: () => void
}
type Bird = {
    name: string
    fly: () => void
}
function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined
}
1
2
3
4
5
6
7
8
9
10
11

在这个例子中,pet is Fish 是我们的类型谓词。谓词的形式是 parameterName is Type,其中 parameterName 必须是当前函数签名中的参数名称。

任何时候 isFish 被调用时,如果原始类型是兼容的,TypeScript 将把该变量缩小到该特定类型。

function getSmallPet(): Fish | Bird {
    let fish: Fish = {
        name: 'gold fish',
        swim: () => {
        }
    }
    let bird: Bird = {
        name: 'sparrow',
        fly: () => {
        }
    }
    return true ? bird : fish
}
// 这里 pet 的 swim 和 fly 都可以访问了
let pet = getSmallPet()
if (isFish(pet)) {
    pet.swim()
} else {
    pet.fly()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注意,TypeScript 不仅知道 pet 在 if 分支中是一条鱼;它还知道在 else 分支中,你没有一条 Fish,所以你一定有一只 Bird 。

你可以使用类型守卫 isFish 来过滤 Fish | Bird 的数组,获得 Fish 的数组。

const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]
const underWater1: Fish[] = zoo.filter(isFish)
// 或者,等同于
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]
// 对于更复杂的例子,该谓词可能需要重复使用
const underWatch3: Fish[] = zoo.filter((pet): pet is Fish => {
	if (pet.name === 'frog') {
    	return false
	}
	return isFish(pet)
})
1
2
3
4
5
6
7
8
9
10
11

# 受歧视的 unions

到目前为止,我们所看的大多数例子都是围绕着用简单的类型(如 string、boolean 和 number)来缩小单个变量。虽然这很常见,但在 JavaScript 中,大多数时候我们要处理的是稍微复杂的结构。

为了激发灵感,让我们想象一下,我们正试图对圆形和方形等形状进行编码。圆记录了它们的半径,方记录了它们的边长。我们将使用一个叫做 kind 的字段来告诉我们正在处理的是哪种形状。这里是定义 Shape 的第一个尝试。

interface Shape {
    kind: "circle" | "square";
    radius?: number;
    sideLength?: number;
}
1
2
3
4
5

注意,我们使用的是字符串字面类型的联合。"circle" 和 "square" 分别告诉我们应该把这个形状当作一个圆形还是方形。通过使用 "circle" | "square " 而不是 string ,我们可以避免拼写错误的问题。

function handleShape(shape: Shape) {
    // oops!
    if (shape.kind === "rect") {
        // ...
    }
}
1
2
3
4
5
6

我们可以编写一个 getArea 函数,根据它处理的是圆形还是方形来应用正确的逻辑。我们首先尝试处理圆形。

function getArea(shape: Shape) {
    return Math.PI * shape.radius ** 2;
}
1
2
3

在 strictNullChecks 下,这给了我们一个错误——这是很恰当的,因为 radius 可能没有被定义。但是如果我们对 kind 属性进行适当的检查呢?

function getArea(shape: Shape) {
    if (shape.kind === "circle") {
        return Math.PI * shape.radius ** 2;
    }
}
1
2
3
4
5

嗯,TypeScript 仍然不知道该怎么做。我们遇到了一个问题,即我们对我们的值比类型检查器知道的更多。我们可以尝试使用一个非空的断言(radius 后面的那个叹号 !)来说明 radius 肯定存在。

function getArea(shape: Shape) {
    if (shape.kind === "circle") {
        return Math.PI * shape.radius! ** 2;
    }
}
1
2
3
4
5

但这感觉并不理想。我们不得不用那些非空的断言对类型检查器声明一个叹号(!),以说服它相信 shape.radius 是被定义的,但是如果我们开始移动代码,这些断言就容易出错。此外,在 strictNullChecks 之外,我们也可以意外地访问这些字段(因为在读取这些字段时,可选属性被认为总是存在的)。我们绝对可以做得更好。

Shape 的这种编码的问题是,类型检查器没有办法根据种类属性知道 radius 或 sideLength 是否存在。我们需要把我们知道的东西传达给类型检查器。考虑到这一点,让我们再来定义一下 Shape。

interface Circle {
    kind: "circle";
    radius: number;
}
interface Square {
    kind: "square";
    sideLength: number;
}
type Shape = Circle | Square;
1
2
3
4
5
6
7
8
9

在这里,我们正确地将 Shape 分成了两种类型,为 kind 属性设置了不同的值,但是 radius 和 sideLength 在它们各自的类型中被声明为必需的属性。

让我们看看当我们试图访问 Shape 的半径时会发生什么。

function getArea(shape: Shape) {
    return Math.PI * shape.radius ** 2;
}
1
2
3

就像我们对 Shape 的第一个定义一样,这仍然是一个错误。当半径是可选的时候,我们得到了一个错误(仅在 strictNullChecks 中),因为 TypeScript 无法判断该属性是否存在。现在 Shape 是一个联合体,TypeScript 告诉我们 shape 可能是一个 Square,而 Square 并没有定义半径 radius。这两种解释都是正确的,但只有我们对 Shape 的新编码仍然在 strictNullChecks 之外导致错误。

但是,如果我们再次尝试检查 kind 属性呢?

function getArea(shape: Shape) {
    if (shape.kind === "circle") {
        // shape: Circle
        return Math.PI * shape.radius ** 2;
    }
}
1
2
3
4
5
6

这就摆脱了错误。当 union 中的每个类型都包含一个与字面类型相同的属性时,TypeScript 认为这是一个有区别的 union,并且可以缩小 union 的成员。

在这种情况下,kind 就是那个共同属性(这就是 Shape 的判别属性)。检查 kind 属性是否为 "circle",就可以剔除 Shape 中所有没有 "circle" 类型属性的类型。这就把 Shape 的范围缩小到了 Circle 这个类型。

同样的检查方法也适用于 switch 语句。现在我们可以试着编写完整的 getArea ,而不需要任何讨厌的叹号 ! 非空的断言。

function getArea(shape: Shape) {
    switch (shape.kind) {
            // shape: Circle
        case "circle":
            return Math.PI * shape.radius ** 2;
            // shape: Square
        case "square":
            return shape.sideLength ** 2;
    }
}
1
2
3
4
5
6
7
8
9
10

这里最重要的是 Shape 的编码。向 TypeScript 传达正确的信息是至关重要的,这个信息就是 Circle 和 Square 实际上是具有特定种类字段的两个独立类型。这样做让我们写出类型安全的 TypeScript 代码,看起来与我们本来要写的 JavaScript 没有区别。从那里,类型系统能够做 正确 的事情,并找出我们 Switch 语句的每个分支中的类型。

作为一个旁观者,试着玩一玩上面的例子,去掉一些返回关键词。你会发现,类型检查可以帮助避免在 Switch 语句中不小心落入不同子句的 bug。

辨证的联合体不仅仅适用于谈论圆形和方形。它们适合于在 JavaScript 中表示任何类型的消息传递方案,比如在网络上发送消息( client/server 通信),或者在状态管理框架中编码突变。

# never 类型与穷尽性检查

在缩小范围时,你可以将一个联合体的选项减少到你已经删除了所有的可能性并且什么都不剩的程度。在这些情况下,TypeScript 将使用一个 never 类型来代表一个不应该存在的状态。

never 类型可以分配给每个类型;但是,没有任何类型可以分配给 never(除了 never 本身)。这意味着你可以使用缩小并依靠 never 的出现在 Switch 语句中做详尽的检查。

例如,在我们的 getArea 函数中添加一个默认值,试图将形状分配给 never,当每个可能的情况都没有被处理时,就会引发。

type Shape = Circle | Square;
function getArea(shape: Shape) {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "square":
            return shape.sideLength ** 2;
        default:
            const _exhaustiveCheck: never = shape;
            return _exhaustiveCheck;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

在 Shape 联盟中添加一个新成员,将导致 TypeScript 错误。

interface Triangle {
    kind: "triangle";
    sideLength: number;
}
1
2
3
4
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "square":
            return shape.sideLength ** 2;
        default:
            const _exhaustiveCheck: never = shape;
            return _exhaustiveCheck;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
编辑此页 (opens new window)
#TypeScript
更新时间: 2025/04/06, 01:04:59
TypeScript Office - 常用类型
TypeScript Office - 更多函数

← TypeScript Office - 常用类型 TypeScript Office - 更多函数→

最近更新
01
技术随笔 - Element Plus 修改包名 原创
11-02
02
Reactor - 扩展性
11-02
03
Reactor - 最佳实践
11-02
更多文章>
Theme by Vdoing | Copyright © 2021-2025 Somnus Haoyuan | MIT License
京公网安备 11010802034042号 京ICP备2025120692号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式