TypeScript: TypeScript 接口 (interface)
最后更新:2026-08-26
接口(interface)是 TypeScript 定义对象形状的核心方式——它描述"一个对象必须有哪些属性和方法",但不提供实现。
1. 接口的基本语法
(1) 定义接口
TYPESCRIPT
interface User {
name: string;
age: number;
email: string;
}
(2) 使用接口
TYPESCRIPT
let user: User = {
name: "Charlie",
age: 20,
email: "xiaoming@example.com"
};
(3) 接口作为函数参数
TYPESCRIPT
function greet(user: User): string {
return `你好,${user.name}!你今年${user.age}岁。`;
}
console.log(greet({ name: "Diana", age: 22, email: "hong@example.com" }));
输出:
TEXT
📖 仅展示
你好,Diana!你今年22岁。
(4) 接口描述方法
接口不仅能描述属性,还能描述方法签名:
TYPESCRIPT
interface Animal {
name: string;
speak(): string;
move(distance: number): void;
}
let dog: Animal = {
name: "旺财",
speak() { return "汪汪!"; },
move(distance) { console.log(`${this.name} 移动了 ${distance} 米`); }
};
console.log(dog.speak()); // "汪汪!"
dog.move(10); // "旺财 移动了 10 米"
2. 可选属性与只读属性
(1) 可选属性 ?
TYPESCRIPT
interface Config {
host: string;
port: number;
debug?: boolean; // 可选
timeout?: number; // 可选
}
// 可以省略可选属性
let config: Config = { host: "localhost", port: 3000 };
let config2: Config = { host: "localhost", port: 3000, debug: true };
(2) 只读属性 readonly
TYPESCRIPT
interface Point {
readonly x: number;
readonly y: number;
}
let point: Point = { x: 1, y: 2 };
// point.x = 10; // ❌ 只读属性不可修改
(3) ReadonlyArray 配合接口
TYPESCRIPT
interface TodoList {
readonly name: string;
readonly items: readonly string[]; // items 数组本身和内容都只读
}
let todo: TodoList = {
name: "今日任务",
items: ["写代码", "测试", "部署"]
};
// todo.items.push("新任务"); // ❌ 只读数组不可修改
// todo.name = "明日任务"; // ❌ 只读属性不可修改
3. 接口继承(extends)
接口可以通过 extends 继承其他接口,实现类型的组合和复用:
(1) 单继承
TYPESCRIPT
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
let emp: Employee = {
name: "Charlie",
age: 28,
employeeId: "E001",
department: "工程部"
};
(2) 多继承
接口可以同时继承多个接口:
TYPESCRIPT
interface Serializable {
serialize(): string;
}
interface Loggable {
log(message: string): void;
}
interface Entity extends Serializable, Loggable {
id: number;
}
let item: Entity = {
id: 1,
serialize() { return JSON.stringify({ id: this.id }); },
log(message) { console.log(`[${this.id}] ${message}`); }
};
(3) 覆写属性
子接口可以覆写父接口的属性类型,但必须兼容:
TYPESCRIPT
interface Base {
data: string | number;
}
interface Derived extends Base {
data: string; // ✅ 收窄——string 是 string | number 的子类型
}
▶ 示例:用继承构建层次化类型
TYPESCRIPT
// 基础接口
interface Shape {
color: string;
}
// 扩展接口
interface Square extends Shape {
sideLength: number;
}
interface Circle extends Shape {
radius: number;
}
// 使用
let square: Square = { color: "红色", sideLength: 10 };
let circle: Circle = { color: "蓝色", radius: 5 };
function describeShape(shape: Shape): string {
return `一个${shape.color}的图形`;
}
console.log(describeShape(square)); // "一个红色的图形"
console.log(describeShape(circle)); // "一个蓝色的图形"
输出:
TEXT
📖 仅展示
一个红色的图形
一个蓝色的图形
4. 声明合并
interface 的独特能力——同名的 interface 会自动合并属性:
(1) 基本合并
TYPESCRIPT
interface Window {
title: string;
}
interface Window {
count: number;
}
// 等价于:
// interface Window {
// title: string;
// count: number;
// }
let win: Window = { title: "主窗口", count: 3 };
(2) 合并的规则
- 同名属性类型必须一致,否则报错
- 方法签名合并为重载
- 后声明的接口排在重载列表前面(优先级更高)
TYPESCRIPT
interface Calculator {
compute(a: number, b: number): number;
}
interface Calculator {
compute(a: string, b: string): string;
}
// 合并后 compute 有两个重载
let calc: Calculator = {
compute(a: any, b: any): any {
return a + b;
}
};
console.log(calc.compute(1, 2)); // 3
console.log(calc.compute("a", "b")); // "ab"
(3) 实际用途:扩展第三方类型
TYPESCRIPT
// 给内置的 Window 接口添加自定义属性
interface Window {
myCustomProperty: string;
}
// 现在可以安全使用
// window.myCustomProperty = "hello"; // ✅
💡 这就是为什么用 interface 而不是 type 扩展第三方库的类型—— type 不支持声明合并,interface 可以。
5. 索引签名与接口
接口中也可以使用索引签名:
TYPESCRIPT
interface StringMap {
[key: string]: string;
}
let translations: StringMap = {
hello: "你好",
goodbye: "再见",
thanks: "谢谢"
};
// 添加新键值对
translations["sorry"] = "对不起";
索引签名和已知属性共存时,已知属性的类型必须兼容:
TYPESCRIPT
interface Config {
[key: string]: string | number;
host: string; // ✅ string 是 string | number 的子类型
port: number; // ✅ number 是 string | number 的子类型
// debug: boolean; // ❌ boolean 不是 string | number 的子类型
}
6. interface 与 type 别名的区别
interface 和 type 都能定义对象类型,但有以下区别:
| 特性 | interface | type |
|---|---|---|
| 对象类型 | ✅ 主要用途 | ✅ 也可以 |
| 声明合并 | ✅ 支持 | ❌ 不支持 |
| 继承 | extends |
& 交叉类型 |
| 联合类型 | ❌ 不能直接定义 | ✅ type A = B | C |
| 基本类型别名 | ❌ 不能 | ✅ type ID = string |
| 计算属性 | ❌ 不支持 | ✅ 支持 |
| instanceof | ✅ class implements | ❌ 不能 |
(1) 什么时候用 interface
- 定义对象形状(API 响应、配置对象、数据模型)
- 需要 extends 继承
- 需要声明合并(扩展第三方类型)
- class implements(类实现接口)
(2) 什么时候用 type
- 联合类型:
type Status = "active" | "inactive" - 基本类型别名:
type ID = string | number - 条件类型、映射类型等高级操作
- 工具类型的底层定义
📌 建议: 对象类型优先用 interface,联合类型和高级类型操作用 type。两者不是互斥的,可以在同一项目中混合使用。
❓ 常见问题
Q interface 和 type 到底该用哪个?
A 简单规则——定义"对象形状"用 interface,定义"类型别名/联合类型"用 type。如果团队已有约定,跟随团队即可。两者 90% 的场景等价,不用过度纠结。
Q 接口可以继承 type 定义的类型吗?
A 可以。interface extends 可以继承任何对象类型的 type 别名。反过来,type 也可以用交叉类型
& 组合 interface。两者互操作无障碍。Q 声明合并有什么风险?
A 声明合并是 interface 的特性也是潜在风险——如果两个同名 interface 有同名但类型不同的属性,会编译报错。实际开发中,声明合并主要用来扩展第三方库的类型定义(.d.ts),日常代码中应避免定义同名 interface。
Q 接口可以描述函数类型吗?
A 可以,但一般用 type 更自然。
interface Fn { (a: string): number } 等价于 type Fn = (a: string) => number,后者更简洁直观。函数类型推荐用 type。📖 小节
- interface 描述对象的形状——有哪些属性、什么类型、什么方法签名
?标记可选属性,readonly标记只读属性;两者可以组合readonly x?: number- interface 用
extends继承,支持单继承和多继承;子接口可以收窄父接口属性类型 - 声明合并是 interface 独有的能力——同名 interface 自动合并,用于扩展第三方类型
- interface 适合对象形状定义,type 适合联合类型和高级操作;日常开发两者混用
📝 作业
- 基础题(难度⭐):定义
Book接口(title、author、pages、isbn?),创建两本书对象并输出。其中 isbn 是可选属性。 - 进阶题(难度⭐⭐):定义
Shape基础接口(color、area 方法),然后定义Circle和Rectangle分别继承 Shape 并添加各自的属性。写一个函数接收 Shape 类型参数并调用 area 方法。 - 挑战题(难度⭐⭐⭐):用声明合并给内置的
Array<T>接口添加一个last(): T | undefined方法。然后在实际数组上调用这个方法,思考为什么这需要配合模块声明。