返回 导航

其他

hangge.com

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)下面是索引类型的使用示例:
提示:索引签名中的属性名(如 indexname)可以任意命名,它只是一个占位符,表示任意同类型的属性名。
// 用 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 表示需要同时满足 numberstring 类型。然而,实际上这种类型是不存在的,因此 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 类,并且实现了 ISwimIEat 接口。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 字面量对象赋值给类型为 IPersonp 变量。在字面量赋值的过程中,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)在实际开发中,interfacetype 都可以用于定义对象类型。主要区别如下:
  • 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)

回到顶部