TypeScript入门到精通——TypeScript类型系统基础——单元类型、顶端类型、尾端类型
1、单元类型
单元类型(Unit Type)也叫作单例类型(Singleton Type),指的是仅包含一个可能值的类型。由于这个特殊的性质,编译器在处理单元类型时甚至不需要关注单元类型表示的具体值。
TypeScript 中的单元类型有以下几种:
-
- undefined 类型
- null 类型
- unique symbol 类型
- void 类型
- 字面量类型
- 联合枚举成员类型
我们能看到这些单元类型均值包含一个可能值。示例如下:
let x: undefined;
let y: null = null;
const sym: unique symbol = Symbol();
function doSomething(): void {
// 函数体
}
const str: "hello" = "hello";
enum Shape {
Circle = "circle",
Square = "square",
Triangle = "triangle"
}
function drawShape(shape: Shape): void {
// 根据 shape 的值绘制不同的形状
}
2、顶端类型
顶端类型(Top Type)源自于数学中的类型论,同时它也被广泛应用于计算机编程语言中。顶端类型是一种通用类型,有时也称为通用超类型,因为在类型系统中,所有类型都是顶端类型的子类型,或者说顶端类型是所有其他类型的父类型。顶端类型涵盖了类型系统中所有可能的值。
TypeScript 中有以下两种顶端类型:
-
- any
- unknown
2.1、any
any
类型是TypeScript中的一个顶级类型,它表示任意类型的值。当你将一个变量声明为any
类型时,该变量可以持有任何类型的值,包括number
、string
、boolean
、object
、null
、undefined
等。在编译时,TypeScript不会对any
类型的值进行任何类型检查,这意味着你可以对该值进行任何操作,而不会引发编译错误。
let value: any = "Hello, world!";
value = 42; // 合法,因为value的类型是any
value = { name: "John" }; // 合法,因为value的类型是any
value.nonExistentProperty = true; // 合法,因为value的类型是any
尽管any
类型在某些情况下很有用,但它会禁用TypeScript的类型检查功能,可能导致运行时错误。因此,应该尽量避免在代码中过度使用any
类型,而是使用更具体的类型。
2.2、unknown 类型
unknown
类型是TypeScript 3.0中引入的一个顶级类型,它表示未知类型的值。与any
类型不同,当你将一个变量声明为unknown
类型时,你不能直接对该值进行任何操作,除非你先进行类型断言或类型检查。
举个例子:
let value: unknown = "Hello, world!";
value = 42; // 合法,因为value的类型是unknown
value = { name: "John" }; // 合法,因为value的类型是unknown
value.nonExistentProperty = true; // 编译错误,因为value的类型是unknown
unknown类型提供了一种更安全的方式来处理不确定类型的值。在进行操作之前,你需要先确定值的实际类型,这可以通过类型断言或类型检查来实现。例如,你可以使用
typeof运算符或
instanceof`运算符来检查值的类型,或者使用类型断言来指定值的类型。
下面是一个使用unknown
类型的例子:
function handleValue(value: unknown) {
if (typeof value === "string") {
console.log(`String value: ${value}`);
} else if (typeof value === "number") {
console.log(`Number value: ${value}`);
} else {
console.log(`Unknown value: ${value}`);
}
}
handleValue("Hello, world!"); // 输出:String value: Hello, world!
handleValue(42); // 输出:Number value: 42
handleValue({ name: "John" }); // 输出:Unknown value: [object Object]
在这个例子中,handleValue
函数的参数被声明为unknown
类型。在函数体内,我们使用typeof
运算符来检查值的类型,并根据不同的类型执行相应的操作。
2.3、类型安全性
为什么有了 any 还要有 unknown 呢?
any
类型在编译时会禁用 TypeScript 的类型检查功能,这意味着你可以对any
类型的值进行任何操作,而不会引发编译错误。然而,这也可能导致运行时错误。相比之下,unknown
类型在编译时保持了类型安全性。如果你尝试对unknown
类型的值进行任何操作,编译器会抛出错误,除非你先进行类型断言或类型检查。
在TypeScript中,使用unknown
类型可以利用逐步类型细化(Type Narrowing)的特性。逐步类型细化是TypeScript的一种推断机制,它允许在条件语句中根据条件的结果缩小值的类型范围。举个例子:
function handleValue(value: unknown) {
if (typeof value === "string") {
// 在这个分支中,value 的类型被细化为 string
console.log(`String value: ${value.toUpperCase()}`); // 这里可以使用 string 类型的方法
} else if (typeof value === "number") {
// 在这个分支中,value 的类型被细化为 number
console.log(`Number value: ${value * 2}`); // 这里可以使用 number 类型的方法
} else {
console.log(`Unknown value: ${value}`);
}
}
在这个例子中,handleValue
函数的参数被声明为unknown
类型。在函数体内,我们使用typeof
运算符来检查值的类型,并根据不同的类型执行相应的操作。由于逐步类型细化的特性,当进入某个条件分支时,我们可以确定value
的具体类型,并且可以使用相应类型的方法。
总结:
-
any
类型在编译时禁用了TypeScript的类型检查功能,可能导致运行时错误。它适用于一些特殊情况,但在大多数情况下应该避免使用。unknown
类型在编译时保持了类型安全性,并且可以利用逐步类型细化的特性。它适用于处理不确定类型的值,并且可以根据需要进行类型检查和类型断言。使用unknown
类型可以提供更好的代码质量和类型安全性。
3、尾端效应
在类型系统中,尾端类型(Bottom Type)是所有其他类型的子类型。由于一个值不可能同时属于所有类型,例如一个值不可能同时为数字类型和字符串类型,因此尾端类型中不包含任何值。尾端类型也称为 0 类型或者空之类。
3.1、never
在 TypeScript 中,never
类型是一个特殊的类型,它表示的是一个永远不会出现的值。这意味着,如果一个函数永远不会返回(例如,总是抛出错误),那么它的返回类型就是never
。
下面是一些never
类型的例子:
// 返回 never 的函数
function error(message: string): never {
throw new Error(message);
}
// inferred type is never
let x = (() => { throw new Error("oops"); })();
// 在这个例子中,我们有一个联合类型 `string | number`,但是我们有一个条件 `x is never`,这个条件永远是 false,所以 `x` 的类型被推断为 `never`。
let x: string | number;
if (x is never) {
// 这个代码块永远不会执行
console.log("x is never");
} else {
console.log("x is not never");
}
never类型在 TypeScript 的类型系统中扮演了很重要的角色,尤其是在处理一些复杂的类型推断和条件类型时。
3.2、应用场景(一)
异常处理:在函数中,如果有一个分支是永远不可能被执行到的,那么这个分支的返回类型就可以被推断为never
。
举个例子:
function error(message: string): never {
throw new Error(message);
}
这个函数永远不会返回一个有效的值,它只会抛出一个错误。因此,它的返回类型是 never
。
3.3、应用场景(二)
无限循环:一个永远不会结束的循环的返回类型也可以是 never
。
举个例子:
function loop(): never { while (true) { // do something } }
这个函数将永远不会返回,因此它的返回类型是 never
。
3.4、应用场景(三)
在TypeScript中,有一些代码是永远不会被执行到的,例如在一个return
语句之后的代码。这些代码的返回类型也可以是never
。
举个例子:
function unreachableCode(): never {
return;
console.log("This code is unreachable"); // Error: Unreachable code detected.
}
这个函数中的console.log
语句是永远不会被执行到的,因此它的返回类型是never
。
总的来说,never
类型在TypeScript中的主要应用场景是表示一个永远不会出现的值的类型,主要用于异常处理、无限循环和不可达的代码。