Dark Dwarf Blog background

TypeScript 接口

TypeScript 接口

在 TypeScript 中,接口(interface)是定义代码“契约”或“形状”的核心工具。它用于描述对象应该包含哪些属性和方法,而不关心其具体的实现。这使得我们能够编写出更加灵活且结构清晰的代码。

1. 接口理念

TypeScript 的接口主要用于描述对象的形状(Shape)。它定义了一个对象必须包含哪些属性,以及这些属性的类型

interface Person {
  name: string;
  age: number;
}

// tom 变量的形状必须严格遵守 Person 接口的定义
let tom: Person = {
  name: "Tom",
  age: 25,
};

接口是一种在编译时进行检查的工具,它不产生任何 JavaScript 代码。它的唯一目的是在开发阶段提供类型检查和智能提示。

type(类型别名)和 interface 在很多场景下可以互换使用,但它们之间存在一些关键区别,这决定了它们的最佳使用场景。

特性interfacetype
扩展性支持 extends 和声明合并不支持,但可通过交叉类型 & 实现
实现类class 可以 implementsclass 可以 implements (但 interface 更惯用)
声明合并支持。多次声明同名接口会合并不支持。多次声明同名类型会报错
适用场景定义对象、类的结构定义联合类型、元组、函数等复杂类型

声明合并 (Declaration Merging)interface 独有的强大特性:

interface User {
  name: string;
}

interface User {
  age: number;
}

// 两个 User 接口会自动合并
const user: User = {
  name: "Alice",
  age: 30,
};

最佳实践:

  • 当定义公共 API 的形状(如库的参数对象)或希望对象结构能够被扩展时,优先使用 interface
  • 当需要定义联合类型、元组或复杂的类型组合时,使用 type

2. 描述对象形状

a.a. 可选属性 (?)

在属性名后添加 ? 表示该属性是可选的。

interface Person {
  name: string;
  age?: number; // age 属性可有可无
}

const person1: Person = { name: "Alice" }; // OK
const person2: Person = { name: "Bob", age: 30 }; // OK

b.b. 只读属性 (readonly)

使用 readonly 关键字定义的属性只能在对象首次创建时被赋值,之后便无法修改。

interface Point {
  readonly x: number;
  readonly y: number;
}

const p: Point = { x: 10, y: 20 };

// p.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.

readonly 是一个编译时约束,它不会改变运行时的行为。如果只读属性是一个对象,该对象内部的属性仍然是可变的。

c.c. 任意属性 (Index Signatures)

索引签名允许接口拥有任意数量的、未预先定义的属性。

interface Config {
  port: number;
  host: string;
  [propName: string]: any; // 索引签名
}

const config: Config = {
  port: 8080,
  host: "localhost",
  ssl: true, // OK,通过了任意属性检查
  retries: 5, // OK
};

注意:一旦定义了索引签名,接口中所有确定属性可选属性的类型都必须是索引签名值类型的子类型

interface Person {
  name: string;
  age?: number; // Error: Property 'age' of type 'number' is not assignable to string index type 'string'.
  [propName: string]: string;
}

3. 描述函数与类

a.a. 描述函数类型

接口可以用来描述一个函数的参数和返回值类型。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

const mySearch: SearchFunc = (src, sub) => {
  return src.search(sub) > -1;
};

b.b. 描述类类型

接口可以强制一个类必须包含某些实例属性和方法,这通过 implements 关键字实现。

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
}

注意:接口只检查类的实例部分,不检查其静态部分(如构造函数)。

4. 接口的继承

一个接口可以使用 extends 关键字继承另一个或多个接口,从而复用和组合已有的类型定义。

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

// Square 继承了 Shape 和 PenStroke
interface Square extends Shape, PenStroke {
  sideLength: number;
}

const square: Square = {
  color: "blue",
  penWidth: 5.0,
  sideLength: 10,
};