TypeScript - 接口interface的使用详解(索引签名、函数类型、接口继承、接口实现、交叉类型)
作者:hangge | 2026-08-12 08:57
面向对象编程具有封装、继承、多态三大特性,而接口是封装实现的最重要的概念之一。接口是在应用程序中定义一种约定的结构,从接口派生的类必须遵循其接口提供的结构。TypeScript 编译器不会将接口转换为 JavaScript,它只用于类型检查。本文将详细介绍 TypeScript 中接口的定义、继承、实现以及与类型别名的区别等内容。




一、基本介绍
1,接口的概念
(1)接口(Interface)是对象的模板,可以看作是一种类型约定。使用了某个接口的对象,就拥有了指定的类型结构。
(2)接口的主要作用是为类型命名和为代码定义契约,它可以帮助我们在开发阶段捕获潜在的类型错误。
2,接口的核心特点
- 接口使用 interface 关键字定义
- 接口中的功能没有具体实现,只定义结构
- 接口不能实例化,不能使用 new 关键字创建对象
- TypeScript 编译器不会将接口转换为 JavaScript
二、使用接口声明对象类型
1,基本用法
(1)TypeScript 中有两种方式声明对象类型:使用类型别名(type)或使用接口(interface)。
(2)下面通过接口的方式为对象声明类型。可以看到,这两种方式只是语法不一样,但功能相同。接口中可以定义三种属性:
- 普通属性:必须有初始值
- 可选属性:使用 ? 标记,可以不提供
- 只读属性:使用 readonly 标记,只能在初始化时赋值
// 方式一:通过类型别名(type)声明对象类型
type IInfoType = {
readonly name: string,
age?: number,
height: number
}
// 方式二:通过接口(interface)声明对象类型
interface IInfoType {
readonly name: string; // 只读属性
age?: number; // 可选属性
height: number; // 必选属性
}
// 指定对象的类型
const info: IInfoType = {
name: "hangge",
age: 18,
height: 1.68
};
console.log(info.name); // 输出: hangge
// info.name = "123"; // 错误:只读属性不能修改
info.age = 20; // ok,可以修改可选属性
三、索引类型
1,索引签名
(1)使用 interface 定义对象类型时,要求对象的属性名、方法以及类型都是确定的。但有时会遇到一些特殊情况,例如所有的 key 或者 value 的类型都是相同的,这时可以使用索引类型。
(2)下面是索引类型的使用示例:
提示:索引签名中的属性名(如 index 或 name)可以任意命名,它只是一个占位符,表示任意同类型的属性名。
// 用 interface 定义索引类型
interface IndexLanguage {
[index: number]: string; // key 是 number 类型,value 是 string 类型
}
const frontLanguage: IndexLanguage = {
0: "HTML",
1: "CSS",
2: "JavaScript",
3: "Vue"
};
// 用 interface 定义另一个索引类型
interface ILanguageYear {
[name: string]: number; // key 是 string 类型,value 是 number 类型
}
const languageYear: ILanguageYear = {
"C": 1972,
"Java": 1995,
"JavaScript": 1996,
"TypeScript": 2014
};
2,混合使用
具体的属性可以和索引签名混合在一起使用,但是属性的类型必须是索引值的子集:
注意:如果定义了具体的属性,该属性的类型必须与索引签名的返回值类型兼容,否则会报错。
interface LanguageBirth {
[name: string]: number; // 索引签名
Java: number; // Java 属性的类型必须是索引值的子集
}
const language: LanguageBirth = {
"Java": 1995,
"JavaScript": 1996,
"C": 1972
};
四、函数类型
1,定义函数类型
(1)interface 不仅可以定义对象中的属性和方法,还可以用于定义函数类型。
(2)下面是用接口定义函数类型的示例:
提示:如果只是定义一个普通的函数类型,推荐使用类型别名 type,这样可以提高代码的可读性。但如果需要更强的扩展性,可以使用接口来定义。
// 方式一:用 type 定义函数类型
// type CalcFn = (n1: number, n2: number) => number
// 方式二:用 interface 定义函数类型
interface CalcFn {
(n1: number, n2: number): number;
}
// 指定 add 函数的类型
const add: CalcFn = (num1, num2) => {
return num1 + num2;
};
// 指定参数 calcFn 函数的类型
function calc(num1: number, num2: number, calcFn: CalcFn) {
return calcFn(num1, num2);
}
calc(20, 30, add); // 输出: 50
2,混合定义
接口可以同时定义函数签名和属性方法:
// 用 interface 定义函数的类型,该函数还有 get 和 post 属性
interface FakeAxiosType {
(config: any): Promise; // 函数签名
get(url: string): string; // get 方法
post(url: string): string; // post 方法
}
const FakeAxios: FakeAxiosType = function(config: any) {
return Promise.resolve(config);
};
FakeAxios.get = function(url: string): string {
return 'get response';
};
FakeAxios.post = function(url: string): string {
return 'post response';
};
console.log(FakeAxios({ url: '/api' })); // Promise
console.log(FakeAxios.get('/users')); // 输出: get response
console.log(FakeAxios.post('/users')); // 输出: post response

五、接口的继承
1,单继承与多继承
(1)在 TypeScript 中,接口和类一样可以实现继承。接口的继承同样使用 extends 关键字,接口支持多继承,而类不支持多继承。
(2)下面是接口继承的示例:
提示:extends 关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性。
interface ISwim {
swimming: () => void; // 接口定义的方法
}
interface IFly {
flying: () => void;
}
// 接口的多继承
interface IAction extends ISwim, IFly {
// 可以添加新的属性或方法
}
// 必须实现接口中的所有方法
const action: IAction = {
swimming() {
console.log("swimming");
},
flying() {
console.log("flying");
}
};
action.swimming(); // 输出: swimming
action.flying(); // 输出: flying

六、交叉类型
1,交叉类型的概念
(1)前面我们已经了解了联合类型,它表示满足多个类型中的任意一个。还有一种类型合并方式:交叉类型(Intersection Type),它表示需要同时满足多个类型的条件,使用符号 & 连接。
- 下面的交叉类型 MyType 表示需要同时满足 number 和 string 类型。然而,实际上这种类型是不存在的,因此 MyType 实际上是一个 never 类型:
// 交叉类型示例:同时满足 number 和 string 类型 // 实际上这种类型是不存在的,是 never 类型 type MyType = number & string;
(2)在开发中,交叉类型通常用于合并对象类型:
提示:交叉类型与接口继承的区别:交叉类型是类型的合并,而继承是属性的复制。两者在某些场景下可以达到相同的效果。
interface ISwim {
swimming: () => void;
}
interface IFly {
flying: () => void;
}
// 联合类型:满足其中一个即可
type MyType1 = ISwim | IFly;
// 交叉类型:必须同时满足两个类型
type MyType2 = ISwim & IFly;
// 联合类型:只需要实现其中一个方法
const obj1: MyType1 = {
flying() {
console.log("flying");
}
};
// 交叉类型:必须实现所有方法
const obj2: MyType2 = {
swimming() {
console.log("swimming");
},
flying() {
console.log("flying");
}
};
七、接口的实现
1,implements 关键字
(1)接口除了可以被继承,还可以被类实现。如果一个类实现了该接口,那么在需要传入该接口的地方,都可以传入该类对应的对象,这就是面向接口编程的思想。
提示:面向接口编程的核心思想是:针对接口编程,而不是针对具体实现编程。这样可以提高代码的灵活性和可扩展性。
(2)下面是接口实现的示例。可以看到,Fish 类继承了 Animal 类,并且实现了 ISwim 和 IEat 接口。Person 类也实现了 ISwim 接口。
interface ISwim {
swimming: () => void;
}
interface IEat {
eating: () => void;
}
class Animal {}
// 继承(extends): 只能实现单继承
// 实现接口(implements): 类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
swimming() {
console.log("Fish Swimming");
}
eating() {
console.log("Fish Eating");
}
}
class Person implements ISwim {
swimming() {
console.log("Person Swimming");
}
}
// 编写公共的 API,面向接口编程
// swimAction 函数接收的是 ISwim 接口
function swimAction(swimable: ISwim) {
swimable.swimming();
}
// 只要实现了 ISwim 接口的类对应的对象,都可以传给 swimAction 函数
swimAction(new Fish()); // 输出: Fish Swimming
swimAction(new Person()); // 输出: Person Swimming

八、字面量赋值
1,额外属性检查
(1)接口可以为对象声明类型,但如果对象中包含了接口中未定义的属性,会提示报错:
interface IPerson {
name: string;
}
const info: IPerson = {
name: "hangge", // ok
age: 18 // 报错:Type '{ name: string; age: number; }' is not assignable to type 'IPerson'
};
(2)针对这个报错有多种解决方案,比如增加 IPerson 中的属性、使用索引类型或交叉类型等。这里介绍另一种方案:使用字面量赋值。
- 可以看到,将 info 字面量对象赋值给类型为 IPerson 的 p 变量。在字面量赋值的过程中,TypeScript 在类型检查时会保留 IPerson 类型,同时擦除其他类型。
interface IPerson {
name: string;
}
// 先定义字面量对象
const info = {
name: "hangge", // ok
age: 18 // ok
};
// 字面量赋值:TypeScript 会擦除(freshness) IPerson 类型之外的类型检查
const p: IPerson = info;
console.log(p.name); // 输出: hangge
2,函数参数的字面量检查
如果将字面量对象直接传给函数的参数,也会进行额外属性检查:
提示:对象字面量会被特殊对待,会经过额外属性检查。如果想绕过这个检查,可以将对象先赋值给一个变量,再传递给函数参数。
interface IPerson {
name: string;
}
function printInfo(person: IPerson) {
console.log(person.name);
}
// 将字面量对象直接传给函数的参数
printInfo({
name: "hangge",
age: 18 // 报错:Object literal may only specify known properties
});
// 对字面量对象的引用,传递给函数参数
const info = {
name: "hangge",
age: 18
};
printInfo(info); // ok,不会报错
附:interface 和 type 的区别
1,主要区别
(1)在实际开发中,interface 和 type 都可以用于定义对象类型。主要区别如下:
- interface:可以重复地对某个接口定义属性和方法
- type:定义的是别名,别名不能重复
(2)下面演示 interface 的重复定义特性:
// interface 可以重复定义
interface IFoo {
name: string;
}
interface IFoo {
age: number;
}
// IFoo 类型是上面两个接口的合并
const foo: IFoo = {
name: "hangge",
age: 18
};
console.log(foo.name, foo.age); // 输出: hangge 18

(3)下面演示 type 不能重复定义的情况:
注意:在定义非对象类型时,通常推荐使用 type,比如联合类型、字面量类型以及函数类型等。
// 类型别名不能重复
type IBar = {
name: string;
};
// 报错:Duplicate identifier 'IBar'
// type IBar = {
// age: number;
// };
全部评论(0)