接口 (Interface)
接口是 TypeScript 中用于定义对象形状的一种强大工具。它们允许你描述对象的结构,包括属性和方法的名称、类型及其可选性等信息。使用接口可以提高代码的可读性和可维护性,并确保在开发过程中遵循一致的数据结构。此外,TypeScript 的接口还支持继承、扩展和声明合并等功能,使得它们非常适合用来构建复杂的类型系统。
定义接口
你可以使用 interface 关键字来定义一个接口。下面是一些基本的例子:
typescript
// 定义一个简单的接口
interface Person {
name: string;
age?: number; // 可选属性
}
// 使用接口
const person: Person = {
name: "Alice",
age: 30, // 可选属性可以省略或提供
};接口的特性
属性和方法:接口可以包含属性(带类型的字段)以及方法签名(不包含实现)。
typescriptinterface Animal { name: string; speak(): void; } class Dog implements Animal { name: string; constructor(name: string) { this.name = name; } speak() { console.log(`${this.name} says woof!`); } }只读属性:使用
readonly关键字可以定义只读属性,这些属性只能在构造函数中赋值。typescriptinterface Point { readonly x: number; readonly y: number; } let p1: Point = { x: 10, y: 20 }; // p1.x = 5; // 错误:不能修改只读属性索引签名:接口可以定义索引签名,以允许任意数量的同类型属性。
typescriptinterface StringArray { [index: number]: string; } let myArray: StringArray = ["Bob", "Fred"];函数类型:接口还可以用来定义函数的类型签名。
typescriptinterface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function (source: string, subString: string) { return source.search(subString) !== -1; };可索引类型:接口可以定义可索引类型,以允许通过特定类型的键访问对象的属性。
typescriptinterface StringDictionary { [key: string]: string; } const names: StringDictionary = { Alice: "Engineer", Bob: "Designer", };扩展接口:接口可以通过继承其他接口来扩展功能。
typescriptinterface 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}`); } }多重继承:接口可以从多个接口继承。
typescriptinterface Named { name: string; } interface Ageable { age: number; } interface Person extends Named, Ageable {} const person: Person = { name: "Alice", age: 30, };混合类型:接口可以组合多种特性,如同时定义属性、方法和函数类型。
typescriptinterface 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;声明合并:如果你有多个同名的接口定义,TypeScript 会自动合并这些接口的属性。
typescriptinterface User { name: string; } interface User { age: number; } const user: User = { name: "Alice", age: 30 }; // 合并后的接口
类型别名 vs 接口选择指南
选择接口:
- 当你需要扩展类型或者利用声明合并时,应该优先考虑接口。
- 如果你正在定义对象的形状,并且希望将来能够轻松地添加新属性或方法,接口可能是一个更好的选择。
- 需要实现面向对象编程中的多态性和继承时,接口更为合适。
选择类型别名:
- 当你需要创建更简洁的类型定义,尤其是对于原始类型、联合类型、交叉类型等情况时,类型别名通常更为合适。
- 对于那些不需要扩展或合并的复杂类型定义,类型别名提供了更大的灵活性。
类型别名 与接口区别
| 特性 | 接口 (Interface) | 类型别名 (Type Alias) |
|---|---|---|
| 可扩展性 | 支持通过继承其他接口来扩展 | 可以使用交叉类型(&)与其他类型组合 |
| 声明合并 | 支持同一个名字的多个接口声明会自动合并 | 不支持声明合并,多次声明会导致编译错误 |
| 类型检查 | 编译器会更宽松地对待接口中的成员顺序 | 编译器严格检查类型别名中成员的顺序 |
| 额外功能 | 支持 implements 和 extends 关键字 | 不支持 implements 和 extends,但可以通过交叉类型模拟 |
| 自引用 | 支持自引用(即在接口内部引用自身) | 支持自引用,但在某些情况下可能需要使用复杂的类型表达式 |
| 联合类型/元组 | 不直接支持联合类型或元组 | 直接支持联合类型、元组等复杂类型 |
总结
接口是 TypeScript 提供的一个强大工具,用于定义对象的形状,支持继承、扩展和声明合并等功能。理解接口的工作原理及其与类型别名的区别,可以帮助你在编写代码时做出更好的设计决策。