本篇翻译整理自 TypeScript Handbook 中 「Everyday Types」 章节。
类型别名(Type Aliases)
有的时候,一个类型会被使用多次,此时我们更希望通过一个单独的名字来引用它。
这就是类型别名(type alias)。所谓类型别名,顾名思义,一个可以指代任意类型的名字。
类型别名的语法是使用type关键字
type ID = number | string;
当你使用类型别名的时候,它就跟你编写的类型是一样的
type UserInputSanitizedString = string;
const userInput: UserInputSanitizedString = "new input";
接口(Interfaces)
接口声明(interface declaration)是命名对象类型的另一种方式
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
TypeScript 只关心传递给 printCoord 的值的结构(structure)——关心值是否有期望的属性。正是这种只关心类型的结构和能力的特性,我们才认为 TypeScript 是一个结构化(structurally)的类型系统。
类型别名和接口的不同
类型别名和接口非常相似,大部分时候,你可以任意选择使用
接口的几乎所有特性都可以在 type 中使用
// Interface
// 通过继承扩展类型
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear = getBear()
bear.name
bear.honey
// --------------------------------------
// Type
// 通过交集扩展类型
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear = getBear();
bear.name;
bear.honey;
两者区别在于:
- 类型别名也许不会实现声明合并,但是接口可以
// An interface can be re-opened
// and new values added:
interface Mammal {
genus: string
}
interface Mammal {
breed?: string
}
const animal: Mammal = {
genus: "1234",
// Fails because breed has to be a string
breed: 1
}
type Reptile = {
genus: string
}
// You cannot add new variables in the same way
type Reptile = {
breed?: string
}
- 接口可能只会被用于声明对象的形状,不能重命名原始类型
// Here's two examples of
// using types and interfaces
// to describe an object
interface AnObject1 {
value: string
}
type AnObject2 = {
value: string
}
// Using type we can create custom names
// for existing primitives:
type SanitizedString = string
type EvenNumber = number
// This isn't feasible with interfaces
interface X extends string { }
类型断言(Type Assertions)
有的时候,你知道一个值的类型,但 TypeScript 不知道
举个例子,如果你使用 document.getElementById,TypeScript 仅仅知道它会返回一个 HTMLElement,但是你却知道,你要获取的是一个 HTMLCanvasElement
这时,你可以使用类型断言将其指定为一个更具体的类型
方式1 --- as语法
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
方式2 --- 尖括号语法 --- tsx中不支持
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
就像类型注解一样,类型断言也会被编译器移除,并且不会影响任何运行时的行为。
双重断言
默认情况下,TypeScript 仅仅允许类型断言转换为一个更加具体或者更不具体的类型。
有的时候,这条规则会显得非常保守,阻止了你原本有效的类型转换。
如果发生了这种事情,你可以使用双重断言,先断言为 any (或者是 unknown),然后再断言为期望的类型:
const x = "hello" as unknown as number;
字面量类型(Literal Types)
除了常见的类型 string 和 number ,我们也可以将类型声明为更具体的数字或者字符串
当一个变量的是使用const进行类型声明的时候,声明的变量则不能被修改,这就会影响 TypeScript 为字面量创建类型。
const str = "Hello World";
// ^? str的类型为 'Hello World'
字面量类型本身并没有什么太大用, 但如果结合联合类型,就显得有用多了。
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left"); // success
printText("G'day, mate", "centre"); // error
同样我们可以创建数字字面量
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
字面量类型也可以和非字面量类型联合一起使用
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 }); // success
configure("auto"); // success
configure("automatic"); // error
布尔字面量本质上是一个特殊的字面量类型。因为只有两种布尔字面量类型,
true和false类型
boolean实际上就是联合类型true | false的别名。
字面量推断(Literal Inference)
当你初始化变量为一个对象的时候,TypeScript 会假设这个对象的属性的值未来会被修
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1; // obj.counter会被推测为number类型,而不会被推测为0
}
这样在实际使用的时候,会导致一些问题
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
// req的url属性和method属性会被推测为string
// 因为在创建 req 和 调用 handleRequest 函数之间,可能还有其他的代码,或许会将 req.method 赋值一个新字符串比如 "Guess"
// 而handleRequest的参数method需要"GET" | "POST"
// 此时就会报错
handleRequest(req.url, req.method); // error
解决方式如下:
类型断言
// Change 1: --- 有意让 req.method 的类型为字面量类型 "GET",这会阻止未来可能赋值为 "GUESS" 等字段”
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2 --- 我知道 req.method 的值是 "GET"
handleRequest(req.url, req.method as "GET");
转换为类型字面量
// as const 效果跟 const 类似,但是对类型系统而言,
// 它可以确保所有的属性都被赋予一个字面量类型,而不是一个更通用的类型比如 string 或者 number
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
null 和 undefined
null 和 undefined默认是其它任意类型的子类型,可以别赋值给其它任意类型变量。
但这往往是bug的源头,所以我们始终推荐开发者开启 strictNullChecks 选项。
当开启 strictNullChecks 选项后,在使用值可能为null或undefined的属性或方法的时候
需要先判断一下是否为null或undefined后,在执行其它处理逻辑
非空断言操作符(后缀 !)(Non-null Assertion Operator)
TypeScript 提供了一个特殊的语法,可以在不做任何检查的情况下,从类型中移除 null 和 undefined
这就是在任意表达式后面写上 ! ,这是一个有效的类型断言,表示它的值不可能是 null 或者 undefined
就像其他的类型断言,非空断言也不会更改任何运行时的行为
function liveDangerously(x?: number | null) {
// x! 表示 x肯定不为null
console.log(x!.toFixed());
}
枚举(Enums)
枚举是 TypeScript 添加的新特性,用于给多个常量值一个方便使用的别名
不同于大部分的 TypeScript 特性,枚举并不是一个类型层面的增量,而是会添加到语言和运行时
枚举的具体使用规则可以查看枚举类型页面了解更多信息
不常见的原始类型(Less Common Primitives)
TypeScript中同样支持JavaScript中一些不常用的数据类型
bigInt
ES2020 引入原始类型 BigInt,用于表示非常大的整数
// 通过BigInt函数创建一个bigint类型数据
const oneHundred: bigint = BigInt(100);
// 通过字面量类型创建一个bigint类型数据
const anotherHundred: bigint = 100n;
symbol
这也是 JavaScript 中的一个原始类型,通过函数 Symbol(),我们可以创建一个全局唯一的引用
const firstName = Symbol("name");
const secondName = Symbol("name");
// error
// firstName === secondName的值恒为false
if (firstName === secondName) {
// ...
}