Skip to content

TypeScript - 接口

TypeScript 中的接口充当一个契约,定义了对象的结构 (shape)。它指定了一个对象 必须 具备的属性和方法的名称及类型,以便被视为与该接口兼容。接口是 TypeScript 结构化类型系统(通常称为“鸭子类型”)的核心特性。

接口只包含成员(属性、方法签名)的声明;它们不提供实现细节。遵循接口结构的责任在于使用该接口的对象或类。

接口纯粹是一种编译时结构。它们不会被编译成 JavaScript,也没有运行时开销。它们的目的完全是为了在开发过程中进行静态类型检查。

使用 interface 关键字,后跟接口名称(根据约定,通常以大写字母 ‘I’ 开头,但这并非强制要求,例如 IPerson 或 Person)。

interface InterfaceName {
propertyName: type;
optionalProperty?: type; // 可选属性使用 ?
readonly readOnlyProperty: type; // 只读属性在初始化后不能修改
methodName(param: type): returnType; // 方法签名
optionalMethod?(param: type): returnType; // 可选方法
}
interface Person {
firstName: string;
lastName: string;
age?: number; // Optional property
readonly id: number;
sayHi: () => string; // Method signature
}
// 符合 Person 接口的对象
const customer: Person = {
firstName: "Tom",
lastName: "Hanks",
id: 101,
sayHi: (): string => {
return "Hi there";
} // age 是可选的,所以可以省略
};
// 另一个符合该接口的对象
const employee: Person = {
firstName: "Jim",
lastName: "Blakes",
age: 35,
id: 205,
sayHi: () => "Hello!!!"
};
console.log("Customer Object");
console.log(customer.firstName); // Output: Tom
console.log(customer.lastName); // Output: Hanks
console.log(customer.sayHi()); // Output: Hi there
// customer.id = 102; // 错误:无法赋值给 'id',因为它是一个只读属性。
console.log("\nEmployee Object");
console.log(employee.firstName); // Output: Jim
console.log(employee.age); // Output: 35

TypeScript 会检查一个对象是否至少拥有接口定义的属性,并且类型正确。额外属性通常是允许的,除非经过特殊检查(例如,直接赋值对象字面量时)。

接口也可以描述函数的签名。

interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
// 使用箭头函数语法
const myOtherSearch: SearchFunc = (source, substring) => {
return source.includes(substring);
};
console.log(mySearch("hello world", "world")); // Output: true
console.log(myOtherSearch("typescript", "java")); // Output: false

用于数组类型的接口 (索引签名)

Section titled “用于数组类型的接口 (索引签名)”

接口可以定义用于访问类数组结构的索引类型以及存储的值的类型。

// 描述一个键为数字、值为字符串的类数组对象
interface StringArray {
[index: number]: string;
}
let myStrArray: StringArray;
myStrArray = ["Bob", "Alice"];
console.log(myStrArray[0]); // Output: Bob
// myStrArray[2] = 123; // 错误:类型 'number' 不能赋值给类型 'string'。
// 描述一个键为字符串、值为数字的对象
interface NumberDictionary {
[index: string]: number;
length: number; // 也可以有其他特定的属性
// name: string; // 错误:类型为 'string' 的属性 'name' 不能赋值给字符串索引类型 'number'。
}
let ageMap: NumberDictionary = {
"Alice": 30,
"Bob": 35,
length: 2
};
console.log(ageMap["Alice"]); // Output: 30
console.log(ageMap.length); // Output: 2
// ageMap["Charlie"] = "forty"; // 错误:类型 'string' 不能赋值给类型 'number'。

注意:如果一个接口同时具有字符串和数字索引签名,则数字索引器返回的类型必须是字符串索引器返回类型的子类型。此外,具有特定名称的属性也必须符合索引签名的类型。

接口可以使用 extends 关键字继承一个或多个其他接口。这允许组合多个接口定义。

interface ChildInterface extends ParentInterface { ... }
interface ChildInterface extends Parent1, Parent2, ... { ... }
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
// Square extends Shape and PenStroke
interface Square extends Shape, PenStroke {
sideLength: number;
}
let mySquare: Square = {
color: "blue",
penWidth: 5.0,
sideLength: 10
};
console.log(`Square Color: ${mySquare.color}`);
console.log(`Square Pen Width: ${mySquare.penWidth}`);
console.log(`Square Side Length: ${mySquare.sideLength}`);

TypeScript 也有类型别名 (type aliases)(使用 type 关键字),它们通常可以达到与接口类似的效果,特别是用于定义对象结构时。

type Point = {
x: number;
y: number;
};
interface IPoint {
x: number;
y: number;
}

主要区别:

  • 可扩展性: 接口可以被多次声明并会合并(声明合并)。接口也可以使用 extends 进行扩展。类型别名创建后不能合并,但可以使用交叉类型 (&) 来组合类型。
  • 实现: 类可以 implement (实现) 接口,以确保它们遵循契约。类不能直接 implement 定义对象结构的类型别名(尽管它们可以通过交叉类型等方式实现从类型别名创建的类型)。
  • 灵活性: 类型别名稍具灵活性;它们不仅可以表示对象结构,还可以表示联合类型 (union types)、交叉类型 (intersection types)、元组 (tuples)、映射类型 (mapped types) 和基本类型别名(例如,type UserID = string;)。

主要使用接口来定义对象结构或类应该实现的契约。使用类型别名来进行更通用的类型定义,包括联合类型、交叉类型、元组,或当你需要接口不提供的功能时。