TypeScript interface 详解

interface 是 TypeScript 中用来定义对象结构、函数类型、类的类型以及其他复杂数据类型的核心机制之一。它提供了一种灵活且强大的方式来描述数据的形状和结构。了解 interface 的概念、用法及相关的高级特性,可以帮助开发者在项目中更好地利用 TypeScript 的类型系统,提升代码的可维护性、可读性和类型安全性。

1. 基础概念

interface 用来定义对象的形状。通过 interface,你可以声明一个对象包含哪些属性、属性的类型以及它们是否是可选的。也可以用来定义函数类型、类实现的结构等。

基本语法

interface InterfaceName {
    propertyName: type;
}

示例

interface Person {
    name: string;
    age: number;
}
 
const person: Person = {
    name: 'John',
    age: 30,
};

2. 可选属性

interface 中的属性默认是必需的,但你可以通过在属性名后面加上 ? 来标记该属性为可选属性。

示例

interface Person {
    name: string;
    age?: number;  // 可选属性
}
 
const person1: Person = { name: 'Alice' };  // 没有 age 也合法
const person2: Person = { name: 'Bob', age: 25 };  // 有 age 也是合法

3. 只读属性

你可以使用 readonly 关键字来标记某个属性为只读,确保该属性的值在对象创建后不能被修改。

示例

interface Point {
    readonly x: number;
    readonly y: number;
}
 
const point: Point = { x: 10, y: 20 };
// point.x = 30;  // 错误:Cannot assign to 'x' because it is a read-only property

4. 函数类型

interface 还可以用来定义函数类型,即规定函数的参数和返回值的类型。

示例

interface Sum {
    (a: number, b: number): number;
}
 
const add: Sum = (a, b) => a + b;

5. 可索引类型

interface 还可以用于描述数组或对象的索引签名。当你不知道属性名时,可以使用可索引签名来定义索引类型。

示例

interface StringArray {
    [index: number]: string;  // 数组的索引是数字,值是字符串
}
 
let arr: StringArray = ['apple', 'banana'];
console.log(arr[0]);  // 'apple'

还可以定义对象的索引签名:

interface Dictionary {
    [key: string]: string;
}
 
const dict: Dictionary = {
    'name': 'Alice',
    'age': '25',
};

6. 类实现接口

interface 可以用来定义类的结构,类必须实现接口定义的所有属性和方法。类通过 implements 关键字来实现接口。

示例

interface Shape {
    area: number;
    calculateArea(): number;
}
 
class Circle implements Shape {
    radius: number;
    area: number = 0;
 
    constructor(radius: number) {
        this.radius = radius;
    }
 
    calculateArea(): number {
        this.area = Math.PI * this.radius ** 2;
        return this.area;
    }
}
 
const circle = new Circle(5);
console.log(circle.calculateArea());  // 输出圆的面积

7. 接口继承

interface 可以通过 extends 关键字继承其他接口,这样可以在一个接口中重用其他接口的属性。

示例

interface Shape {
    area: number;
    calculateArea(): number;
}
 
interface ColoredShape extends Shape {
    color: string;
}
 
class ColoredCircle implements ColoredShape {
    radius: number;
    area: number = 0;
    color: string;
 
    constructor(radius: number, color: string) {
        this.radius = radius;
        this.color = color;
    }
 
    calculateArea(): number {
        this.area = Math.PI * this.radius ** 2;
        return this.area;
    }
}
 
const coloredCircle = new ColoredCircle(5, 'red');
console.log(coloredCircle.color);  // 'red'
console.log(coloredCircle.calculateArea());  // 圆的面积

8. 接口与类型别名的区别

虽然 interfacetype 都可以用来定义对象、函数或其他类型的结构,但两者有一些细微的区别。

  • 接口(interface) 主要用来描述对象、类的结构。
  • 类型别名(type) 可以用于更广泛的用途,除了可以定义对象的结构外,还可以用于联合类型、交叉类型等。

示例:接口和类型别名的对比

// 接口定义
interface Person {
    name: string;
    age: number;
}
 
// 类型别名定义
type PersonType = {
    name: string;
    age: number;
};
 
const person1: Person = { name: 'Alice', age: 30 };
const person2: PersonType = { name: 'Bob', age: 25 };

9. 动态接口(索引签名)

如果你不确定一个对象会有多少属性,或者这些属性的名称会随着时间变化,可以使用索引签名。

示例

interface Car {
    brand: string;
    [key: string]: string;  // 可以有任意多个额外的字符串属性
}
 
const car: Car = {
    brand: 'Toyota',
    color: 'red',
    model: 'Corolla',
};

10. 接口的合并

interface 具有声明合并的能力。也就是说,如果同一个 interface 被多次声明,TypeScript 会自动将它们合并为一个接口。这对于扩展现有的接口非常有用。

示例

interface Person {
    name: string;
}
 
interface Person {
    age: number;
}
 
const person: Person = { name: 'Alice', age: 30 };  // 合并后的 Person 接口

11. 高级用法

  • 条件类型:结合条件类型和接口,可以创建更灵活的类型系统。
  • 映射类型:可以通过映射类型来构建新类型,如将接口的属性变为可选、只读等。
  • 泛型接口:接口还可以与泛型一起使用,使得接口能够处理不同类型的数据。

示例:泛型接口

interface Box<T> {
    value: T;
}
 
const box: Box<number> = { value: 123 };
const stringBox: Box<string> = { value: 'Hello' };

总结

interface 是 TypeScript 中一个非常重要的工具,帮助开发者定义结构化的数据类型。它的应用非常广泛,可以用于:

  • 定义对象的形状;
  • 用于函数的类型定义;
  • 类的实现;
  • 继承和扩展现有类型。

通过 interface,你可以精确地描述数据的结构、功能和约束,确保在代码执行时符合预期的类型,从而提高代码的可维护性和类型安全。