Skip to content

常见类型

string、number、boolean

如果你对这些类型的值使用 JavaScript typeof 运算符,这些名称与你看到的名称相同。

Array

number[] 或者 Array<number>

在介绍泛型时,我们将了解有关语法 T<U> 的更多信息。

any

当你不希望特定值导致类型检查错误时,你可以使用它。

当你不想写出一个长类型只是为了让 TypeScript 相信某行代码是可以的时,any 类型很有用。

  • 如果你没有指定类型,并且 TypeScript 无法从上下文中推断它,编译器通常会默认为 any。

  • 不过,您通常希望避免这种情况,因为 any 没有进行类型检查。 配置noImplicitAny 将任何隐式 any 标记为错误。

变量的类型注释

使用 const、var 或 let 声明变量时,可以选择添加类型注释以显式指定变量的类型:

TypeScript
let myName: string = "Alice";
let myName: string = "Alice";

但是,在大多数情况下,这不是必需的。TypeScript 会尽可能自动推断代码中的类型。

TypeScript
let myName = "Alice"; // 等同上面 TypeScript 会自动推断类型
let myName = "Alice"; // 等同上面 TypeScript 会自动推断类型

Functions

  1. TypeScript 允许您指定函数的输入输出值的类型。
TypeScript
function setFavoriteNumber(name: number) {};
function getFavoriteNumber():number {};
function setFavoriteNumber(name: number) {};
function getFavoriteNumber():number {};
  1. 如果要注释返回 Promise 的函数的返回类型,则应使用 Promise 类型:
TypeScript
async function getFavoriteNumber(): Promise<number> {
  return 8;
}
async function getFavoriteNumber(): Promise<number> {
  return 8;
}
  1. 匿名函数与函数声明略有不同。当一个函数出现在 TypeScript 可以确定如何调用它的地方时,该函数的参数会自动被赋予类型。
TypeScript
const names = ["Alice", "Bob", "Eve"];
 
names.forEach(function (s) {
  console.log(s.toUpperCase());
});

names.forEach((s) => {
  console.log(s.toUpperCase());
});
const names = ["Alice", "Bob", "Eve"];
 
names.forEach(function (s) {
  console.log(s.toUpperCase());
});

names.forEach((s) => {
  console.log(s.toUpperCase());
});

即使参数 s 没有类型注释,TypeScript 也使用 forEach 函数的类型以及数组的推断类型来确定 s 将具有的类型。

此过程称为上下文类型,因为函数发生的上下文会告知它应该具有什么类型。

Object

要定义对象类型,我们只需列出其属性及其类型。

TypeScript
function printCoord(pt: { x: number; y: number }) {
  console.log(pt.x);
  console.log(pt.y);
}
printCoord({ x: 8, y: 8 });
function printCoord(pt: { x: number; y: number }) {
  console.log(pt.x);
  console.log(pt.y);
}
printCoord({ x: 8, y: 8 });

可选属性:对象类型还可以指定其部分或全部属性是可选的。为此,请在属性名称后添加 ?

TypeScript
function printName(obj: { first: string; last?: string }) {
  // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
function printName(obj: { first: string; last?: string }) {
  // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

在 JavaScript 中,如果你访问一个不存在的属性,你将得到值 undefined,而不是运行时错误。因此,当你从可选属性中读取时,你必须在使用它之前检查 undefined。

TypeScript
function printName(obj: { first: string; last?: string }) {
  console.log(obj.last?.toUpperCase());
}
function printName(obj: { first: string; last?: string }) {
  console.log(obj.last?.toUpperCase());
}

联合类型

联合类型是由两个或多个其他类型组成的类型,表示可能是其中任何一种类型的值。

TypeScript
function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
function printId(id: number | string) {
  console.log("Your ID is: " + id);
}

使用注意:TypeScript 仅在对联合的每个成员都有效时才允许作。

例如,如果你有 union string | number,则不能使用仅在 string 上可用的方法:

TypeScript
function printId(id: number | string) {
  console.log(id.toUpperCase()); // Property 'toUpperCase' does not exist on type 'string | number'.
}
function printId(id: number | string) {
  console.log(id.toUpperCase()); // Property 'toUpperCase' does not exist on type 'string | number'.
}

解决方案是缩小与代码的联合,就像在没有类型注释的 JavaScript 中一样。当 TypeScript 可以根据代码的结构为值推断出更具体的类型时,就会发生收缩。

只有 string 值才会具有 typeof 值 “string”:

TypeScript
function printId(id: number | string) {
  if (typeof id === "string") {    // 收缩为string
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}
function printId(id: number | string) {
  if (typeof id === "string") {    // 收缩为string
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

Array.isArray:

TypeScript
function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {         // 收缩为Array
    console.log("Hello, " + x.join(" and "));
  } else {
    console.log("Welcome lone traveler " + x);
  }
}
function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {         // 收缩为Array
    console.log("Hello, " + x.join(" and "));
  } else {
    console.log("Welcome lone traveler " + x);
  }
}

type

Type -- 类型别名:任何类型的名称。类型别名的语法为:

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

type ID = number | string;

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

type ID = number | string;

type UserInputSanitizedString = string;

interfaces

我们只关心类型的结构和功能,这就是我们称 TypeScript 为结构类型类型系统的原因。

TypeScript
interface Animal {
  name: string;
}

interface Bear extends Animal {
  honey: boolean;
}

const bear = getBear();
bear.name;
bear.honey;
interface Animal {
  name: string;
}

interface Bear extends Animal {
  honey: boolean;
}

const bear = getBear();
bear.name;
bear.honey;

type 与 interface 的区别

  1. 拓展(继承):interface使用 extends,type使用 &
  2. 重写(覆盖):interface可以声明合并,type不可以。
  3. 重命名基元:接口只能用于声明对象的形状,而不能用于重命名基元,type可以。
  4. 性能:对于编译器来说,使用带有 extends 的interface通常比使用带有交集的type性能更高。
  5. 错误:interface名称将始终以其原始形式显示在错误消息中。

断言

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

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

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas"); // 等同上面
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement; // as

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas"); // 等同上面

文本类型(Literals)

除了通用类型 string 和 number 之外,我们可以在类型位置引用特定的 strings 和 numbers。

var 和 let 都允许更改变量中保存的内容,而 const 则不允许。这反映在 TypeScript 如何为 Literals 创建类型中。

TypeScript
let x: "hello" = "hello"; // 定义x 为 hello 文本类型,其值只能为hello 不能修改为其他值

let changingString = "Hello World"; // TypeScript默认推断为string
changingString = "Olá Mundo"; // 可以修改

const constantString = "Hello World"; // TypeScript默认推断为 Hello World 不可修改
let x: "hello" = "hello"; // 定义x 为 hello 文本类型,其值只能为hello 不能修改为其他值

let changingString = "Hello World"; // TypeScript默认推断为string
changingString = "Olá Mundo"; // 可以修改

const constantString = "Hello World"; // TypeScript默认推断为 Hello World 不可修改
就其本身而言,文本类型并不是很有价值:拥有一个只能有一个值的变量没有多大用处!

但是通过将 Literals 组合成联合,你可以表达一个更有用的概念:

TypeScript
function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText('hello','left') // 他的第二个参数只能为 '"left" | "right" | "center"'
function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText('hello','left') // 他的第二个参数只能为 '"left" | "right" | "center"'

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

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

与非 Literal 类型结合使用:

TypeScript
interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}

文字推理 (Literal Inference):当您使用对象初始化变量时,TypeScript 会假定该对象的属性稍后可能会更改值。

TypeScript
const obj = { counter: 0 };
obj.counter = 1;
const obj = { counter: 0 };
obj.counter = 1;

TypeScript 不会假设将 1 分配给以前为 0 的字段是错误的。另一种说法是 obj.counter 必须具有类型 number,而不是 0,因为类型用于确定读取和写入行为。

这同样适用于字符串:

TypeScript
declare function handleRequest(url: string, method: "GET" | "POST"): void;

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method); 
// 报错:Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
declare function handleRequest(url: string, method: "GET" | "POST"): void;

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method); 
// 报错:Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

在上面的例子中,req.method 被推断为 string,而不是 “GET”。因为可以在创建 req 和调用 handleRequest 之间评估代码,这可能会为 req.method 分配一个新字符串,比如 “GUESS”,所以 TypeScript 认为这段代码有错误。

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

TypeScript
// 方法一
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");

// 方法二
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
// 方法一
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");

// 方法二
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

as const 后缀的作用类似于 const,但对于类型系统,确保所有属性都分配有文字类型,而不是更通用的版本,如 string 或 number。

null、undefined

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

TypeScript 有两个同名的相应类型。这些类型的行为方式取决于您是否启用了 strictNullChecks 选项。

strictNullChecks:关闭

仍然可以正常访问可能为 null 或 undefined 的值,并且可以将值 null 和 undefined 分配给任何类型的属性。这类似于没有 null 检查的语言(例如 C#、Java)的行为方式。缺乏对这些值的检查往往是 bug 的主要来源;我们始终建议人们在他们的代码库中启用 strictNullChecks(如果这样做可行)。

strictNullChecks:开启

启用 strictNullChecks 后,当值为 null 或 undefined 时,您需要先测试这些值,然后再对该值使用方法或属性。就像在使用可选属性之前检查 undefined 一样,我们可以使用收缩来检查可能为 null 的值:

TypeScript
function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}
function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}

非空断言运算符 (postfix ):

TypeScript 还具有一种特殊的语法,用于从类型中删除 null 和 undefined,而无需执行任何显式检查。在任何表达式之后写入 !实际上是一个类型断言,该值不是 null 或 undefined:

TypeScript
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());
}
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());
}

Enums

不太常见的基元

TypeScript
const oneHundred: bigint = BigInt(100);

const firstName = Symbol("name");
const oneHundred: bigint = BigInt(100);

const firstName = Symbol("name");