Skip to content

接口 (Interface)

接口是 TypeScript 中用于定义对象形状的一种强大工具。它们允许你描述对象的结构,包括属性和方法的名称、类型及其可选性等信息。使用接口可以提高代码的可读性和可维护性,并确保在开发过程中遵循一致的数据结构。此外,TypeScript 的接口还支持继承、扩展和声明合并等功能,使得它们非常适合用来构建复杂的类型系统。

定义接口

你可以使用 interface 关键字来定义一个接口。下面是一些基本的例子:

typescript
// 定义一个简单的接口
interface Person {
  name: string;
  age?: number; // 可选属性
}

// 使用接口
const person: Person = {
  name: "Alice",
  age: 30, // 可选属性可以省略或提供
};

接口的特性

  1. 属性和方法:接口可以包含属性(带类型的字段)以及方法签名(不包含实现)。

    typescript
    interface Animal {
      name: string;
      speak(): void;
    }
    
    class Dog implements Animal {
      name: string;
    
      constructor(name: string) {
        this.name = name;
      }
    
      speak() {
        console.log(`${this.name} says woof!`);
      }
    }
  2. 只读属性:使用 readonly 关键字可以定义只读属性,这些属性只能在构造函数中赋值。

    typescript
    interface Point {
      readonly x: number;
      readonly y: number;
    }
    
    let p1: Point = { x: 10, y: 20 };
    // p1.x = 5; // 错误:不能修改只读属性
  3. 索引签名:接口可以定义索引签名,以允许任意数量的同类型属性。

    typescript
    interface StringArray {
      [index: number]: string;
    }
    
    let myArray: StringArray = ["Bob", "Fred"];
  4. 函数类型:接口还可以用来定义函数的类型签名。

    typescript
    interface SearchFunc {
      (source: string, subString: string): boolean;
    }
    
    let mySearch: SearchFunc;
    mySearch = function (source: string, subString: string) {
      return source.search(subString) !== -1;
    };
  5. 可索引类型:接口可以定义可索引类型,以允许通过特定类型的键访问对象的属性。

    typescript
    interface StringDictionary {
      [key: string]: string;
    }
    
    const names: StringDictionary = {
      Alice: "Engineer",
      Bob: "Designer",
    };
  6. 扩展接口:接口可以通过继承其他接口来扩展功能。

    typescript
    interface Named {
      name: string;
    }
    
    interface Greetable extends Named {
      greet(phrase: string): void;
    }
    
    class Greeter implements Greetable {
      name: string;
    
      constructor(n: string) {
        this.name = n;
      }
    
      greet(phrase: string) {
        console.log(`${phrase} ${this.name}`);
      }
    }
  7. 多重继承:接口可以从多个接口继承。

    typescript
    interface Named {
      name: string;
    }
    
    interface Ageable {
      age: number;
    }
    
    interface Person extends Named, Ageable {}
    
    const person: Person = {
      name: "Alice",
      age: 30,
    };
  8. 混合类型:接口可以组合多种特性,如同时定义属性、方法和函数类型。

    typescript
    interface Counter {
      (start: number): string;
      interval: number;
      reset(): void;
    }
    
    // (start: number): string; 表示 Counter 是一个可调用的函数,它接受一个 number 类型的参数并返回一个 string。
    
    function getCounter(): Counter {
      let counter = <Counter>function (start: number) {};
      counter.interval = 123;
      counter.reset = function () {};
      return counter;
    }
    
    const c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;
  9. 声明合并:如果你有多个同名的接口定义,TypeScript 会自动合并这些接口的属性。

    typescript
    interface User {
      name: string;
    }
    
    interface User {
      age: number;
    }
    
    const user: User = { name: "Alice", age: 30 }; // 合并后的接口

类型别名 vs 接口选择指南

  • 选择接口

    • 当你需要扩展类型或者利用声明合并时,应该优先考虑接口。
    • 如果你正在定义对象的形状,并且希望将来能够轻松地添加新属性或方法,接口可能是一个更好的选择。
    • 需要实现面向对象编程中的多态性和继承时,接口更为合适。
  • 选择类型别名

    • 当你需要创建更简洁的类型定义,尤其是对于原始类型、联合类型、交叉类型等情况时,类型别名通常更为合适。
    • 对于那些不需要扩展或合并的复杂类型定义,类型别名提供了更大的灵活性。

类型别名 与接口区别

特性接口 (Interface)类型别名 (Type Alias)
可扩展性支持通过继承其他接口来扩展可以使用交叉类型(&)与其他类型组合
声明合并支持同一个名字的多个接口声明会自动合并不支持声明合并,多次声明会导致编译错误
类型检查编译器会更宽松地对待接口中的成员顺序编译器严格检查类型别名中成员的顺序
额外功能支持 implementsextends 关键字不支持 implementsextends,但可以通过交叉类型模拟
自引用支持自引用(即在接口内部引用自身)支持自引用,但在某些情况下可能需要使用复杂的类型表达式
联合类型/元组不直接支持联合类型或元组直接支持联合类型、元组等复杂类型

总结

接口是 TypeScript 提供的一个强大工具,用于定义对象的形状,支持继承、扩展和声明合并等功能。理解接口的工作原理及其与类型别名的区别,可以帮助你在编写代码时做出更好的设计决策。

Released under the MIT License.