TypeScript 接口(interface)完全指南:语法、特性与实战技巧
🔥 吃透 TypeScript ,interface 的使用方法、继承规则、接口合并,以及与 type 的核心区别
一、接口的基础用法
1. 接口的定义与实现
接口通过 interface 关键字定义,内部描述对象的属性和方法类型。任何使用该接口作为类型的对象,都必须严格遵循接口的结构约定。
typescript
// 定义接口 Person
interface Person {
firstName: string;
lastName: string;
age: number;
}
// 实现接口:对象必须包含 firstName、lastName、age 三个属性
const p: Person = {
firstName: 'John',
lastName: 'Smith',
age: 25
};
// 错误示例:缺少 lastName 属性,不符合接口约定
// const p2: Person = { firstName: 'Jane', age: 22 };
2. 提取接口属性的类型
与对象类型一致,接口支持使用方括号 [] 提取某个属性的具体类型,便于单独复用该类型。
typescript
interface Foo {
a: string;
b: number;
}
// 提取属性 a 的类型 → string
type AType = Foo['a'];
// 提取属性 b 的类型 → number
type BType = Foo['b'];
const aVal: AType = 'hello';
const bVal: BType = 123;
二、接口的成员类型
接口的成员包含 5 种形式:对象属性 、对象属性索引 、对象方法 、函数 、构造函数,覆盖了对象的所有常见语法。
1. 对象属性
这是接口最基础的成员类型,通过 属性名: 类型 的形式声明,支持可选属性和只读属性。
typescript
interface Point {
// 必选属性
x: number;
y: number;
// 可选属性:属性名后加 ?
z?: number;
// 只读属性:属性名前加 readonly
readonly id: string;
}
const point: Point = {
x: 10,
y: 20,
id: 'P001'
// z 可选,可省略
};
// 错误示例:只读属性 id 无法修改
// point.id = 'P002';
属性分隔规则 :属性之间可用 ; 或 , 分隔,最后一个属性的结尾分隔符可省略。
2. 对象的属性索引
当对象的属性名不确定(如动态键名)时,可使用属性索引 描述属性名和属性值的类型,支持 string、number、symbol 三种索引类型。
(1)字符串索引
用于约束「属性名为字符串」的对象,所有字符串属性的类型必须符合索引声明。
typescript
// 字符串索引:属性名为 string,属性值为 number
interface StringMap {
[prop: string]: number;
}
const map: StringMap = {
a: 1,
b: 2
};
// 错误示例:属性 c 的值为 string,不符合索引类型约束
// const map2: StringMap = { a: 1, c: 'hello' };
(2)数值索引
用于约束「属性名为数值」的对象,常用来描述类数组对象或数组。
typescript
// 数值索引:属性名为 number,属性值为 string
interface NumberMap {
[prop: number]: string;
}
// 数组符合数值索引约束
const arr: NumberMap = ['a', 'b', 'c'];
// 类数组对象也符合约束
const obj: NumberMap = {
0: 'x',
1: 'y'
};
(3)索引的约束规则
- 唯一性:一个接口中最多只能定义一个字符串索引、一个数值索引。
- 兼容性 :若同时定义字符串索引和数值索引,数值索引的类型必须兼容字符串索引的类型(因为 JS 会将数值属性名自动转为字符串)。
typescript
// 错误:数值索引类型(string)与字符串索引类型(number)不兼容
interface A {
[prop: string]: number;
[prop: number]: string;
}
// 正确:数值索引类型(number)兼容字符串索引类型(number)
interface B {
[prop: string]: number;
[prop: number]: number;
}
3. 对象的方法
接口中声明对象方法有三种写法,效果完全一致,推荐使用函数签名写法(更贴近方法的定义习惯)。
typescript
// 写法 1:函数签名(推荐)
interface MethodInterface1 {
f(x: boolean): string;
}
// 写法 2:箭头函数类型
interface MethodInterface2 {
f: (x: boolean) => string;
}
// 写法 3:函数类型字面量
interface MethodInterface3 {
f: { (x: boolean): string };
}
// 实现接口方法
const obj1: MethodInterface1 = {
f(x) {
return x ? 'true' : 'false';
}
};
方法的重载
接口支持函数重载,即声明多个同名方法的不同参数和返回值类型,无需给出实现,只需约定类型即可。
typescript
// 接口内声明方法重载
interface OverloadInterface {
f(): number;
f(x: boolean): boolean;
f(x: string, y: string): string;
}
// 实现重载方法:需在外部定义函数实现所有重载类型
function func(): number;
function func(x: boolean): boolean;
function func(x: string, y: string): string;
function func(
x?: boolean | string,
y?: string
): number | boolean | string {
if (x === undefined && y === undefined) return 1;
if (typeof x === 'boolean') return x;
if (typeof x === 'string' && typeof y === 'string') return x + y;
throw new Error('参数错误');
}
// 部署重载方法
const obj: OverloadInterface = { f: func };
4. 函数
接口可以直接声明独立的函数类型,用于约束函数的参数和返回值。
typescript
// 声明函数接口:接收两个 number 参数,返回 number
interface Add {
(x: number, y: number): number;
}
// 实现函数接口
const myAdd: Add = (x, y) => x + y;
console.log(myAdd(1, 2)); // 输出 3
5. 构造函数
接口中使用 new 关键字可以声明构造函数类型,用于约束类的构造函数。
typescript
// 声明构造函数接口:接收可选 string 参数,返回 Error 实例
interface ErrorConstructor {
new (message?: string): Error;
}
// 原生 Error 类符合该接口约束
const MyError: ErrorConstructor = Error;
const err = new MyError('出错了');
三、接口的继承
接口支持通过 extends 关键字继承其他类型,实现类型复用与扩展。继承的类型可以是接口 、type 定义的对象类型 、类。
1. 接口继承接口
这是最常见的继承场景,子接口会继承父接口的所有成员,并可添加新成员。
typescript
// 父接口 Shape
interface Shape {
name: string;
}
// 子接口 Circle 继承 Shape
interface Circle extends Shape {
radius: number; // 新增属性
}
// 实现子接口:需包含 name 和 radius
const circle: Circle = {
name: 'circle',
radius: 10
};
多重继承
接口支持同时继承多个父接口,多个父接口用逗号分隔,子接口会合并所有父接口的成员。
typescript
interface Style {
color: string;
}
interface Shape {
name: string;
}
// 多重继承:同时继承 Style 和 Shape
interface Circle extends Style, Shape {
radius: number;
}
// 实现:需包含 color、name、radius
const circle: Circle = {
color: 'red',
name: 'circle',
radius: 10
};
继承的冲突处理
- 子接口与父接口的同名属性必须类型兼容,否则会报错。
- 多重继承时,多个父接口的同名属性也必须类型兼容,否则会报错。
typescript
interface Foo {
id: string;
}
// 错误:子接口 Bar 的 id 类型(number)与父接口 Foo 的 id 类型(string)不兼容
interface Bar extends Foo {
id: number;
}
interface Baz {
id: number;
}
// 错误:父接口 Foo 和 Baz 的 id 类型冲突
interface Qux extends Foo, Baz {
type: string;
}
2. 接口继承 type
接口可以继承 type 命令定义的对象类型 ,实现类型扩展。注意:若 type 定义的是非对象类型(如联合类型),接口无法继承。
typescript
// type 定义对象类型 Country
type Country = {
name: string;
capital: string;
};
// 接口继承 type,新增 population 属性
interface CountryWithPop extends Country {
population: number;
}
const china: CountryWithPop = {
name: '中国',
capital: '北京',
population: 1400000000
};
3. 接口继承类
接口可以继承类,会继承该类的所有成员(包括实例属性和方法),但不包括类的实现。
typescript
class A {
x: string = '';
y(): boolean {
return true;
}
}
// 接口 B 继承类 A,新增 z 属性
interface B extends A {
z: number;
}
// 实现接口 B:需包含 x、y 方法、z
const b: B = {
x: 'hello',
y: () => true,
z: 123
};
注意事项
如果被继承的类包含私有成员(private) 或保护成员(protected),接口虽然可以继承,但无法用于对象实现(因为对象无法拥有私有/保护成员),只能被其他类实现(且需继承该父类)。
四、接口合并
TypeScript 的一个独特特性:多个同名接口会自动合并为一个接口 。这一特性非常适合扩展第三方库的类型(如为 window 对象添加自定义属性)。
1. 基本合并规则
同名接口的属性会合并,方法会形成函数重载。
typescript
// 第一个 Box 接口
interface Box {
height: number;
width: number;
}
// 第二个 Box 接口
interface Box {
length: number;
}
// 合并后的 Box 接口:包含 height、width、length
const box: Box = {
height: 10,
width: 20,
length: 30
};
2. 合并的冲突处理
- 同名属性的类型必须完全一致,否则会报错。
- 同名方法会形成函数重载 ,且后定义的接口方法优先级更高,排在重载列表的前面。
typescript
// 错误:同名属性 a 的类型冲突(number vs string)
interface A {
a: number;
}
interface A {
a: string;
}
// 方法合并形成函数重载
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
// 合并后等同于
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}
特殊优先级:字面量类型参数
如果函数重载的参数是字面量类型,该方法会拥有最高优先级,排在重载列表的最前面,不受定义顺序影响。
typescript
interface A {
f(x: any): void;
}
interface A {
f(x: 'foo'): boolean;
}
// 合并后:字面量类型参数的方法排在前面
interface A {
f(x: 'foo'): boolean;
f(x: any): void;
}
3. 接口合并的实战场景
最常见的用途是扩展全局对象的类型 ,比如为浏览器的 window 对象添加自定义属性。
typescript
// 扩展 Document 接口,添加 foo 属性
interface Document {
foo: string;
}
// 现在可以安全地使用 document.foo,不会报错
document.foo = 'hello';
五、接口联合类型的同名属性
当两个接口组成联合类型时,如果存在同名属性,该属性的类型会自动合并为联合类型。
typescript
interface Circle {
area: bigint;
}
interface Rectangle {
area: number;
}
// 联合类型:Circle | Rectangle
declare const shape: Circle | Rectangle;
// 同名属性 area 的类型为 bigint | number
const area: bigint | number = shape.area;
六、interface 与 type 的异同
interface 和 type 都可以描述对象类型,在很多场景下可以互换,但两者存在核心区别,适用于不同的使用场景。
1. 相同点
- 都可以描述对象类型,约定属性和方法的类型。
- 都支持扩展:
interface通过extends扩展,type通过交叉类型&扩展。
typescript
// interface 扩展
interface Animal {
name: string;
}
interface Bear extends Animal {
honey: boolean;
}
// type 扩展(交叉类型)
type AnimalType = {
name: string;
};
type BearType = AnimalType & {
honey: boolean;
};
2. 核心区别
| 特性 | interface | type |
|---|---|---|
| 支持的类型 | 仅支持对象类型(包括数组、函数) | 支持任意类型(对象、原始类型、联合类型、交叉类型等) |
| 扩展方式 | 通过 extends 关键字继承 |
通过交叉类型 & 实现扩展 |
| 声明合并 | 支持同名接口自动合并 | 不支持同名类型,重复定义会报错 |
| 属性映射 | 不支持映射类型 | 支持映射类型(如 [Key in keyof T]) |
| this 关键字 | 支持在方法中使用 this |
不支持在对象类型中使用 this |
3. 选型建议
- 优先使用 interface:当需要描述对象类型,且可能需要扩展或合并时(如组件 props、API 响应数据结构)。
- 优先使用 type:当需要描述非对象类型(如联合类型、原始类型),或需要使用映射类型时。
七、核心总结
- 接口的本质:是对象类型的模板,约定对象的结构与类型,实现该接口的对象必须严格遵循约定。
- 接口的成员:支持对象属性、属性索引、方法、函数、构造函数五种形式,覆盖对象的所有常见语法。
- 接口的继承:可继承接口、type 对象类型、类,多重继承需注意属性类型兼容性。
- 接口合并:同名接口自动合并,属性合并、方法形成重载,适合扩展第三方类型。
- 与 type 的区别:interface 专注对象类型,支持继承和合并;type 更灵活,支持任意类型。