Interface
Interface(接口)是 TypeScript 类型系统的核心构建块,为类型检查和代码设计提供了强大的契约机制。它通过定义对象的"形状"和结构,确保代码的类型安全,同时作为构建可维护、可扩展应用的基础工具。
在实际开发中,接口广泛应用于定义 API 响应结构、DTO 数据传输对象、服务层契约等场景,是连接不同系统模块的重要桥梁。
基础概念
接口定义
接口通过 interface 关键字声明,用于定义对象的形状(Shape),即对象应该包含哪些属性以及这些属性的类型。
基本语法
interface Point {
x: number;
y: number;
}2
3
4
interface关键字用于声明接口- 属性类型声明使用冒号 : 指定类型(注意:这不是赋值操作)
- 属性之间可以用分号 ; 或逗号 , 分隔,两者均可接受
- 接口是纯类型结构,在编译成
JavaScript时会被完全擦除
使用示例
正确使用
TypeScript// 定义一个接口 interface Point { x: number; y: number; } // 实现接口 let point: Point = { x: 10, y: 20 };1
2
3
4
5
6
7
8
9
10
11错误使用
TypeScript// 错误示例1:缺少属性 const invalidPoint1: Point = { x: "10" }; // Error: 缺少 y 属性,且 x 应为 number 类型 // 错误示例2:类型错误 const invalidPoint2: Point = { x: 10, y: "20" }; // Error: y 的类型应该是 number,而不是 string // 错误示例3:多余属性 const extraPoint: Point = { x: 10, y: 20, z: 30 }; // Error: 对象字面量只能指定已知属性,z 不存在于 Point 类型中1
2
3
4
5
6
7
8
9
10
11
TIP
TypeScript 对对象字面量进行严格的多余属性检查,这是为了防止拼写错误导致的潜在 bug
扩展应用
除了基础对象结构,接口还可以定义多种复杂类型:
函数类型接口
TypeScriptinterface MathOperation { (a: number, b: number): number; } const add: MathOperation = (x, y) => x + y; const multiply: MathOperation = (x, y) => x * y; console.log(add(5, 3)); // 输出: 8 console.log(multiply(5, 3)); // 输出: 151
2
3
4
5
6
7
8
9数组类型接口(索引签名)
TypeScriptinterface StringArray { [index: number]: string; } const names: StringArray = ["Alice", "Bob", "Charlie"]; console.log(names[0]); // 输出: Alice1
2
3
4
5
6注意
通过索引签名定义的数组接口不具备
JavaScript数组的原型方法(如push、pop、length)。如果需要完整的数组功能,建议使用string[]或Array<string>语法。混合类型接口
TypeScriptinterface Counter { count: number; increment(): void; reset(): void; } class SimpleCounter implements Counter { count = 0; increment() { this.count++; } reset() { this.count = 0; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
接口特性
可选属性:灵活的类型约束
在属性名后添加 ? 表示该属性是可选的,这在处理 API 响应、表单数据等场景中非常实用。
interface User {
name: string; // 必选属性
age?: number; // 可选属性
gender?: string; // 可选属性
readonly createdAt: Date; // 只读属性
}
// 以下两种写法都是合法的
const user1: User = { name: "Alice" };
const user2: User = { name: "Bob", age: 25 };2
3
4
5
6
7
8
9
10
接口合并:强大的声明扩展
TypeScript 允许同名接口自动合并,每个接口的属性会被合并到结果接口中,这在扩展第三方类型定义时非常有用。
// 第一次声明
interface Database {
host: string;
}
// 第二次声明(可以是不同文件)
interface Database {
port: number;
}
// 等效于:
interface Database {
host: string;
port: number;
}
const db: Database = {
host: "localhost",
port: 3306
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意
接口合并时,属性的类型会被合并,如果属性的类型不同,会报错。
合并规则
同名属性必须类型兼容
TypeScriptinterface MergeTest { prop: number; } interface MergeTest { prop: string; // Error: 后续声明必须与之前声明使用相同类型 }1
2
3
4
5
6
7方法重载是允许的
TypeScriptinterface Logger { log(message: string): void; } interface Logger { log(message: string, level: number): void; // 合法的方法重载 }1
2
3
4
5
6
7
继承机制
extends 基础用法
接口继承通过 extends 关键字实现,允许新接口包含父接口的所有成员。
interface PublicPoint {
x: number;
y: number;
}
interface Point3D extends PublicPoint {
z: number;
}
const point3d: Point3D = {
x: 10,
y: 20,
z: 30
};2
3
4
5
6
7
8
9
10
11
12
13
14
多接口继承
TypeScript 支持接口同时继承多个父接口。
interface Loggable {
log(message: string): void;
}
interface Serializable {
serialize(): string;
}
interface Entity extends Loggable, Serializable {
id: number;
version: number;
}
class UserEntity implements Entity {
id = 1;
version = 1;
log(message: string) {
console.log(`[UserEntity] ${message}`);
}
serialize() {
return JSON.stringify({ id: this.id, version: this.version });
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
继承 vs 交叉类型
| 特性 | 接口继承 (extends) | 交叉类型 (&) |
|---|---|---|
| 语义 | "是一个"关系 | 类型组合 |
| 同名属性处理 | 必须兼容 | 合并为 never(冲突时) |
| 可读性 | 层次清晰 | 扁平结构 |
| 适用场景 | 类型层次体系 | 临时类型组合 |
// 继承方式
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
}
// 交叉类型方式
type Cat = Animal & {
breed: string;
meow(): void;
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
特殊接口类型
函数类型接口
函数类型接口通过定义调用签名来描述函数类型。
interface MathOperation {
(operand1: number, operand2: number): number;
description?: string; // 可附加属性
}
const add: MathOperation = (a, b) => a + b;
add.description = "Returns the sum of two numbers";
console.log(add(5, 3)); // 输出: 8
console.log(add.description); // 输出: Returns the sum of two numbers2
3
4
5
6
7
8
9
10
构造函数类型
构造函数类型接口用于描述类的构造函数,允许在接口中定义类的属性和方法。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
function createClock(
ctor: ClockConstructor,
h: number,
m: number
): ClockInterface {
return new ctor(h, m);
}
class DigitalClock implements ClockInterface {
constructor(private h: number, private m: number) {}
tick() {
console.log(`${this.h}:${this.m}`);
}
}
const digital = createClock(DigitalClock, 12, 30);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
索引类型接口
数组模式
数组模式接口用于描述数组的类型,允许在接口中定义数组的元素类型和索引类型。
interface StringArray {
[index: number]: string;
}
const names: StringArray = ["Alice", "Bob"];
console.log(names[0]); // 输出: Alice2
3
4
5
6
重要限制
使用索引签名定义的"数组"不具备 JavaScript 数组的原型方法
interface StringArray {
[index: number]: string;
}
const names: StringArray = ["Alice", "Bob"];
// names.push("Charlie"); // Error: Property 'push' does not exist
// console.log(names.length); // Error: Property 'length' does not exist2
3
4
5
6
7
解决方案
扩展接口或使用类型交叉
// 方案1:扩展接口
interface CompleteStringArray {
[index: number]: string;
length: number;
push(...items: string[]): number;
pop(): string | undefined;
}
// 方案2:类型交叉
type EnhancedArray = StringArray & string[];2
3
4
5
6
7
8
9
10
对象模式
interface TypedDictionary<T> {
[key: string]: T;
}
const wordCount: TypedDictionary<number> = {
"hello": 3,
"world": 5
};
const userInfo: TypedDictionary<string> = {
"name": "Alice",
"email": "alice@example.com"
};2
3
4
5
6
7
8
9
10
11
12
13
绕开属性检查
ypeScript 的对象字面量会进行严格的多余属性检查。这在防止拼写错误的同时,有时也会带来不便。以下是三种常见的解决方案:
1. 类型断言:显式类型覆盖
interface StrictType {
id: number;
name: string;
}
const looseData = { id: 1, name: "Alice", age: 25 };
const strictData: StrictType = looseData as StrictType; // 强制类型断言2
3
4
5
6
7
使用场景:处理第三方库的不完整类型、渐进式迁移 JavaScript 代码
风险提示:类型断言会绕过类型检查,可能导致运行时错误
2. 索引签名:推荐方案
interface FlexibleConfig {
required: string;
optional?: number;
[key: string]: unknown; // 允许任意额外属性
}
const config: FlexibleConfig = {
required: "value",
optional: 100,
customData: { key: "value" },
metadata: ["tag1", "tag2"]
};2
3
4
5
6
7
8
9
10
11
12
推荐理由:
- 类型安全:明确告知 TypeScript 可能有额外属性
- 可读性好:清晰表达接口的设计意图
- 维护性强:不需要在每个使用处添加断言
3. 类型兼容:不推荐的方式
interface Account {
username: string;
password: string;
}
function login({ username }: Account) {
// 可能忽略重要的 password 校验
console.log(`Logging in ${username}`);
}
login({ username: "admin", password: "123" }); // 无类型错误但存在安全隐患2
3
4
5
6
7
8
9
10
11
不推荐原因:这种方式虽然能绕过检查,但可能导致重要的校验逻辑被忽略。
方案对比与选择
| 方案 | 适用场景 | 安全性 | 推荐程度 |
|---|---|---|---|
| 索引签名 | 动态属性(API 响应、配置对象) | 高 | 推荐 |
| 类型断言 | 临时绕过类型检查 | 中 | 谨慎使用 |
| 类型兼容 | 快速访问已知属性 | 低 | 不推荐 |

