Skip to content

TypeScript 的类型系统

TypeScript 提供了 JavaScript 的所有功能,并在这些功能之上添加了一层: TypeScript 的类型系统。

例如,JavaScript 提供了诸如 string 和 number 这样的原始类型,但它不检查你在赋值时与类型是否匹配。TypeScript 提供了这样的功能。

这意味着你现有的运行良好的 JavaScript 代码也是 TypeScript 代码。TypeScript 的主要好处是,它可以检查代码中的意外行为,从而降低出现错误的机会。

类型推断

TypeScript 可以识别 JavaScript 语言,在许多情况下可以推断类型。在创建变量并将其赋值给特定值时, TypeScript 将使用该值作为其类型。

TypeScript
let helloWorld = "Hello World"; // 类型推断为 string
let helloWorld = "Hello World"; // 类型推断为 string

TypeScript 通过感知 JavaScript 的工作原理,TypeScript 可以构建一个接受 JavaScript 代码但具有类型的类型系统。这个类型系统使得我们不需要添加额外的字符来显式地指定类型。在上面的例子中,TypeScript就是这样知道 helloWorld 是 string 类型。

定义类型

你可以在 JavaScript 中使用各种各样的设计模式。然而,某些设计模式使得类型难以自动推断。(例如,使用动态编程的模式)

为了使类型推断涵盖这些情况, TypeScript 支持扩展 JavaScript 语言,它可以让 TypeScript 知道如何去推断类型

例如

javascript
// 这样的对象结构
const user = {
  name: "Hayes",
  id: 0,
};
// 这样的对象结构
const user = {
  name: "Hayes",
  id: 0,
};

可以使用 interface 关键字声明显式地描述此对象的内部数据的类型

TypeScript
interface User {
  name: string;
  id: number;
}
const user: User = {
  name: "Hayes",
  id: 0,
};
interface User {
  name: string;
  id: number;
}
const user: User = {
  name: "Hayes",
  id: 0,
};

JavaScript 支持类和面向对象编程,TypeScript 也支持。你可以将接口声明与类一起使用

TypeScript
interface User {
  name: string;
  id: number;
}
class UserAccount {
  name: string;
  id: number;
  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}
const user:User = new UserAccount('Bob',1);
interface User {
  name: string;
  id: number;
}
class UserAccount {
  name: string;
  id: number;
  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}
const user:User = new UserAccount('Bob',1);

可以使用接口对参数进行注释,并将值返回给函数:

TypeScript
interface User {
  name: string;
  id: number;
}
function getAdminUser(): User { ... } // 返回值
function deleteUser(user: User) { ... } // 参数
interface User {
  name: string;
  id: number;
}
function getAdminUser(): User { ... } // 返回值
function deleteUser(user: User) { ... } // 参数

拓展 (interface 与 type 的区别)

构建类型有两种语法: 接口类型。 你应该更喜欢 interface。当需要特定功能时使用 type 。

interface:优先用 interface,当需要定义对象结构或利用声明合并时(例如为第三方库扩展类型)。

type:用 type 处理复杂类型,如联合类型、元组、或需要类型操作(如 Pick<T, K>、Omit<T, K> 等工具类型)。

特性interfacetype
声明合并
扩展方式extends&(交叉类型)
复杂类型(联合、元组)
工具提示友好性更友好复杂类型可能冗长
主要用途描述对象结构类型别名或复杂类型

组合类型

使用 TypeScript,可以通过组合简单类型来创建复杂类型。有两种流行的方法可以做到这一点:联合和泛型

联合

使用联合,可以声明类型可以是许多类型中的一种。例如,可以将 boolean 类型描述为 true 或 false :

TypeScript
type MyBool = true | false;
type MyBool = true | false;

联合类型的一个流行用法是描述 string 或者 number 的字面量的合法值。

TypeScript
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type PositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type PositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;

联合也提供了一种处理不同类型的方法。例如,可能有一个函数处理 array 或者 string:

TypeScript
function getLength(obj: string | string[]) {
  return obj.length;
}
function getLength(obj: string | string[]) {
  return obj.length;
}

泛型

泛型为类型提供变量

它允许在定义函数、接口、类时不预先指定具体的类型,而是在使用时再指定类型的一种特性。泛型的好处是可以增加代码的灵活性和可重用性,同时保持类型的严格性。

TypeScript
function identity<T>(arg: T): T {
  return arg;
}
function identity<T>(arg: T): T {
  return arg;
}

一个常见的例子是数组。没有泛型的数组可以包含任何内容。带有泛型的数组可以描述数组包含的值。

TypeScript
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;

你可以声明自己使用泛型的类型:

TypeScript
interface Backpack<Type> {
  add: (obj: Type) => void;
  get: () => Type;
}
interface Backpack<Type> {
  add: (obj: Type) => void;
  get: () => Type;
}

结构化的类型系统

TypeScript 的一个核心原则是类型检查基于对象的属性和行为。这有时被叫做“鸭子类型”或“结构类型”(structural typing)。

在结构化的类型系统当中,如果两个对象具有相同的结构,则认为它们是相同类型的。

TypeScript
interface Point {
  x: number;
  y: number;
}
 
function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}
 
// 打印 "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);
interface Point {
  x: number;
  y: number;
}
 
function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}
 
// 打印 "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);

point 变量从未声明为 Point 类型。 但是,在类型检查中,TypeScript 将 point 的结构与 Point的结构进行比较。它们的结构相同,所以代码通过了。

结构匹配只需要匹配对象字段的子集。

Typescript
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3);
// 打印 "12, 26"
 
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); 
// 打印 "33, 3"
 
const color = { hex: "#187ABF" };
logPoint(color);
// Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'.
// Type '{ hex: string; }' is missing the following properties from type 'Point': x, y
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3);
// 打印 "12, 26"
 
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); 
// 打印 "33, 3"
 
const color = { hex: "#187ABF" };
logPoint(color);
// Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'.
// Type '{ hex: string; }' is missing the following properties from type 'Point': x, y

类和对象确定结构的方式没有区别。

Typescript
interface Point {
  x: number;
  y: number;
}
 
function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

class VirtualPoint {
  x: number;
  y: number;
 
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}
 
const newVPoint = new VirtualPoint(13, 56);
logPoint(newVPoint); // 打印 "13, 56"
interface Point {
  x: number;
  y: number;
}
 
function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

class VirtualPoint {
  x: number;
  y: number;
 
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}
 
const newVPoint = new VirtualPoint(13, 56);
logPoint(newVPoint); // 打印 "13, 56"