【TypeScript】TypeScript常用类型(下篇)

阅读 180

2022-10-11


目录

  • ​​前言​​
  • ​​1、联合类型​​
  • ​​2、类型别名​​
  • ​​3、接口​​
  • ​​4、类型断言​​
  • ​​5、文字类型​​
  • ​​6、null和undefined​​
  • ​​7、枚举​​
  • ​​8、不太常见的原语​​

前言

在上篇中讲到了​​TypeScript​​​中的​​基元类型​​​,​​数组​​​,​​any​​​,​​函数​​​,​​对象​​​,在这一篇中将继续深入​​TypeScript​​​的常用类型,包括​​联合类型​​​,​​类型别名​​​,​​接口​​​,​​类型断言​​​等,在学习之前建议先了解一下上篇的内容,安装并配置好​​tsc​​ 编译器。

1、联合类型

  • 定义联合类型

联合类型是由两个或多个其他类型组成的类型,表示可能是这些类型中的任何一种的值。我们将这些类型中的每一种称为联合类型的成员。

多个类型之间使用​​|​​分割:

function getId(id: string | number) {
console.log("id=", id);
}
getId(1);
getId("1");

这个例子中​​getId​​​接收的参数​​id​​​可为​​string​​​类型也可为​​number​​类型,当类型不匹配时依旧会抛出错误:

【TypeScript】TypeScript常用类型(下篇)_前端

  • 使用联合类型

在使用联合类型时需要注意的是:不能盲目将联合类型的数据当成单独类型的数据进行操作,不然​​TypeScript​​将抛出错误提醒你:

【TypeScript】TypeScript常用类型(下篇)_typescript_02

这里直接对联合类型​​id​​​进行字符串上的​​toUpperCase​​​操作,​​TypeScript​​​会自动检测​​id​​​联合类型的成员是否都具有​​toUpperCase​​​属性,这里检测到联合类型的成员​​number​​​类型并不具有​​toUpperCase​​属性,所以会抛出错误提示用户。

正确的做法是:

function getId(id: string | number) {
if (typeof id === "string") {
// 在此分支中,TS自动检测id的类型为“string”
console.log(id.toUpperCase());
} else {
// 此处,TS自动检测id的类型为“number”
console.log(id.toString());
}
}

先使用判断语句确定​​id​​具体的类型,再对其进行操作(这称为类型缩小,博主后期会出另外的博文对其详细介绍),​​TypeScript​​​会非常智能的检测判断分支的语句中​​id​​的类型。

2、类型别名

前面我们声明类型都是直接在类型注释中编写类型来使用它们,这很方便,但是想要多次使用同一个类型,并用一个名称来引用它是很常见的。

这就可以使用类型别名​​type​​来声明类型:

type Id = number | string;
// 在类型注释中直接使用类型别名
function getId(id: Id) {
console.log("id=", id);
}
getId(1);
getId("1");

在定义类型别名以及后面讲到的接口时,都建议将首字母进行大写,如上面例子中的​​Id​

  • 类型别名也可以声明复杂的类型:

type Point = {
x: number;
y: number;
};

function printCoord(pt: Point) {
console.log("坐标x的值是: " + pt.x);
console.log("坐标y的值是: " + pt.y);
}
printCoord({ x: 100, y: 100 });

  • 扩展类型(交叉类型)

类型别名可以使用交叉点​​&​​来扩展类型:

type User = {
name: string;
};
type Admin = User & {
isAdmin: boolean;
};

const admin: Admin = {
name: "Ailjx",
isAdmin: true,
};

这里​​Admin​​​在​​User​​​基础上扩展了​​isAdmin​​​类型,当使用​​Admin​​并赋予的类型不匹配时将抛出错误:

【TypeScript】TypeScript常用类型(下篇)_typescript_03


【TypeScript】TypeScript常用类型(下篇)_.net_04


注意这里报的错,在下面的接口与类型别名的区别中会详细分析这个报错。

梳理一下,之所以称其为类型别名,就是因为它只是用一个名称来指向一种类型,当用户需要使用该种类型时可直接使用该名称,方便复用,也方便将类型与业务代码抽离开来。

3、接口

一个接口声明​​interface​​ 是另一种方式来命名对象类型:

interface Point {
x: number;
y: number;
}
// 与前面的示例完全相同
function printCoord(pt: Point) {
console.log("坐标x的值是: " + pt.x);
console.log("坐标y的值是: " + pt.y);
}
printCoord({ x: 100, y: 100 });

  • 类型别名和接口之间的差异

类型别名和接口非常相似,在很多情况下你可以自由选择它们。几乎所有的功能都在​​interface​​​ 中可用​​type​​ ,关键区别在于扩展新类型的方式不同:

前面提到类型别名是通过交叉点​​&​​​来扩展类型,而接口的扩展是使用​​extends​​​ 继承(这与​​class​​类的继承相似):

interface User {
name: string;
}
interface Admin extends User {
isAdmin: boolean;
}

const admin: Admin = {
name: "Ailjx",
isAdmin: true,
};

继承后的​​Admin​​​接口包含父类​​User​​​中的所有类型,当使用​​Admin​​并赋予的类型不匹配时将抛出错误:

【TypeScript】TypeScript常用类型(下篇)_javascript_05

这里对比类型注意中抛出的错误会发现这么一个细节
当我们不给使用​​​Admin​​​类型的常量​​admin​​​添加​​name​​​属性时,使用类型别名扩展的会提示:​​但类型 "User" 中需要该属性​​​,而使用接口扩展的会提示:​​但类型 "Admin" 中需要该属性​

从这里我们能看出类型别名的扩展是将父类​​User​​​与扩展的类型​​{ isAdmin: boolean;}​​​一并交给​​Admin​引用,当使用Admin时实际是同时使用了​​User​​​和​​{ isAdmin: boolean;}​​两种类型。

而接口的扩展是直接继承父类​​User​​​,在父类基础上添加了​​{ isAdmin: boolean;}​​​并生成一个新类型​​Admin​​​,使用​​Admin​​​时仅仅是使用了​​Admin​​​,与​​User​​无关了

​interface​​也可以向现有的接口添加新字段:

interface MyWindow {
title: string;
}
interface MyWindow {
count: number;
}
const w: MyWindow = {
title: "hello ts",
count: 100,
};

同名的​​interface​​​会被​​TypeScript​​合并到一起,这是类型别名所做不到的:

【TypeScript】TypeScript常用类型(下篇)_.net_06

  • 在​​TypeScript 4.2​​​ 版之前,类型别名​​可能出现在错误消息中​​,有时会代替等效的匿名类型(这可能是可取的,也可能是不可取的)。接口将始终在错误消息中命名。
  • 类型别名可能不参与​​声明合并,但接口可以​​。
  • 接口只能用于​​声明对象的形状,不能重命名基元​​。
  • 接口名称将​​始终以其原始形式出现在错误消息中​​,但仅当它们按名称使用时。

建议优先使用接口,接口满足不了时再使用类型别名

4、类型断言

有时,你会获得有关 ​​TypeScript​​ 不知道的值类型的信息。

例如,如果你正在使用​​document.getElementById​​​ ,​​TypeScript​​​ 只知道这将返回某种类型的​​HTMLElement​​​ ,但你可能知道你的页面将始终具有​​HTMLCanvasElement​​​ 给定 ​​ID​​ 的值 。

在这种情况下,你可以使用类型断言​​as​​来指定更具体的类型:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

与类型注释一样,类型断言由编译器删除,不会影响代码的运行时行为。

还可以使用尖括号语法(除非代码在​​.tsx​​ 文件中),它是等效的:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

提醒:因为类型断言在编译时被移除,所以没有与类型断言相关联的运行时检查。​​null​​ 如果类型断言错误,则不会出现异常。

​TypeScript​​ 只允许类型断言转换为更具体或不太具体的类型版本。此规则可防止“不可能”的强制,例如:

【TypeScript】TypeScript常用类型(下篇)_.net_07

类型断言应该用于:在​​TypeScript​​ 没有推断出确切的类型,而你又非常坚定其确切的类型是什么的情况

5、文字类型

除了一般类型​​string​​​ 和​​number​​ ,我们可以在类型位置引用特定的字符串和数字,来限定变量只能为特定的值:

let MyName: "Ailjx";

【TypeScript】TypeScript常用类型(下篇)_typescript_08

就其本身而言,文字类型并不是很有价值,拥有一个只能有一个值的变量并没有多大用处!

但是通过将文字组合成联合,你可以表达一个更有用的概念——例如,只接受一组特定已知值的函数:

function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");

​alignment​​​只能被赋予​​left​​​、​​right​​​或​​center​​,这在组件库中非常常见!

数字文字类型的工作方式相同:

function compare(a: number, b: number): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}

当然,你可以将这些与非文字类型结合使用:

interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic"); // 报错

【TypeScript】TypeScript常用类型(下篇)_javascript_09

还有一种文字类型:布尔文字。只有两种布尔文字类型,它们是类型​​true​​​ 和​​false​​​ 。类型​​boolean​​​ 本身实际上只是联合类型​​true​​​ | ​​false​​ 的别名。

  • 文字推理

看这样一段代码:

function handleRequest(url: string, method: "GET" | "POST" | "GUESS") {
// ...
}
const req = {
url: "https://blog.net/m0_51969330?type=blog",
method: "GET",
};
handleRequest(req.url, req.method);

感觉没毛病是吧,但其实TypeScript会抛出错误:

【TypeScript】TypeScript常用类型(下篇)_typescript_10


在上面的例子​​req.method​​​ 中推断是​​string​​​ ,不是​​"GET"​​​ 。因为代码可以在创建​​req​​​ 和调用之间进行评估,​​TypeScript​​ 认为这段代码有错误。

有两种方法可以解决这个问题。

1. 可以通过在任一位置添加类型断言来更改推理
方案一:

const req = {
url: "https://blog.net/m0_51969330?type=blog",
method: "GET" as "GET",
};
>

表示:“我确定​​req.method​​​ 始终拥有文字类型 ​​"GET"​​ ”

方案二:

handleRequest(req.url, req.method as "GET");

表示:“我知道​​req.method​​​ 具有​​"GET"​​ 值”。

2. 可以使用as const 将类型转换为类型文字

将整个对象转换成类型文字:

const req = {
url: "https://blog.net/m0_51969330?type=blog",
method: "GET",
} as const;

只将​​method​​转换成类型文字:

const req = {
url: "https://blog.net/m0_51969330?type=blog",
method: "GET" as const,
};

该​​as const​​​ 后缀就像​​const​​​ 定义,确保所有属性分配的文本类型,而不是一个更一般的​​string​​​ 或​​number​​ 。

6、null和undefined

​JavaScript​​​ 有两个原始值用于表示不存在或未初始化的值: ​​null​​​ 和​​undefined​

​TypeScript​​​ 有两个对应的同名类型。这些类型的行为取决于您是否设置​​tsconfig.json/strictNullChecks​​ 选择。

​strictNullChecks​​表示在进行类型检查时,是否考虑“null”和“undefined”

  • ​strictNullChecks=false​​时,下述代码不会报错:

function doSomething(x: string | null) {
console.log("Hello, " + x.toUpperCase());
}

  • ​strictNullChecks=true​​​时(​​strict= true时所有的严格类型检查选项都默认为true​​),上述代码会报错:

【TypeScript】TypeScript常用类型(下篇)_javascript_11


避免报错正确的做法:

function doSomething(x: string | null) {
if (x === null) {
// 做一些事
} else {
console.log("Hello, " + x.toUpperCase());
}
}

  • 非空断言运算符( ! 后缀)

​!​​ 在任何表达式之后写入实际上是一种类型断言,即确定该值不是​​null​​​ or ​​undefined​​ :

function liveDangerously(x?: number | null) {
// console.log(x.toFixed()); // 报错:对象可能为 "null" 或“未定义”。
console.log(x!.toFixed()); // 正确
}

就像其他类型断言一样,这不会更改代码的运行时行为,因此仅当你知道该值不能是​​null​​​ 或​​undefined​​​ 时​​!​​ 的使用才是重要的。

7、枚举

枚举是 ​​TypeScript​​​ 添加到 ​​JavaScript​​ 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。

与大多数 ​​TypeScript​​​ 功能不同,这不是​​JavaScript​​ 的类型级别的添加,而是添加到语言和运行时的内容。因此,你确定你确实需要枚举在做些事情,否则请不要使用。可以​​在Enum 参考页​​ 中阅读有关枚举的更多信息。

​TS​​枚举:

enum Direction {
Up = 1,
Down,
Left,
Right,
}
console.log(Direction.Up) // 1

编译后的​​JS​​代码:

"use strict";
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
console.log(Direction.Up); // 1

8、不太常见的原语

  • bigint

从 ​​ES2020​​​ 开始,​​JavaScript​​​ 中有一个用于非常大的整数的原语​​BigInt​​ :

// 通过bigint函数创建bigint
const oneHundred: bigint = BigInt(100);
// 通过文本语法创建BigInt
const anotherHundred: bigint = 100n;

主意:使用​​BigInt​​​和​​bigint​​​时需要将​​tsconfig.json​​​中的​​target​​​设置成​​es2020​​​以上(包含​​es2020​​)的版本

你可以在​​TypeScript 3.2 发行说明​​ 中了解有关 BigInt 的更多信息。

  • symbol

​JavaScript​​​ 中有一个原语​​Symbol()​​ ,用于通过函数创建全局唯一引用:

const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// 这里的代码不可能执行
}

【TypeScript】TypeScript常用类型(下篇)_前端_12


精彩评论(0)

0 0 举报