TypeScript中接口与类型别名的对比

TypeScript中接口与类型别名的对比

技术背景

在TypeScript中,接口(Interface)和类型别名(Type alias)是两种用于定义类型的方式。它们在很多场景下可以互换使用,但也存在一些关键的区别。理解这些区别对于编写高质量的TypeScript代码至关重要。

实现步骤

1. 对象与函数的描述

  • 接口
1
2
3
4
5
6
7
8
interface Point {
x: number;
y: number;
}

interface SetPoint {
(x: number, y: number): void;
}
  • 类型别名
1
2
3
4
5
6
type Point = {
x: number;
y: number;
};

type SetPoint = (x: number, y: number) => void;

2. 其他类型的定义

类型别名可以用于定义原始类型、联合类型和元组类型等,而接口则不能。

1
2
3
4
5
6
7
8
9
10
11
12
// 原始类型
type Name = string;

// 对象
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// 联合类型
type PartialPoint = PartialPointX | PartialPointY;

// 元组类型
type Data = [number, string];

3. 扩展

接口和类型别名都可以扩展,但语法不同。

  • 接口扩展接口
1
2
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
  • 类型别名扩展类型别名
1
2
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
  • 接口扩展类型别名
1
2
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
  • 类型别名扩展接口
1
2
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

4. 实现

类可以实现接口或类型别名,但不能实现联合类型的类型别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Point {
x: number;
y: number;
}

class SomePoint implements Point {
x = 1;
y = 2;
}

type Point2 = {
x: number;
y: number;
};

class SomePoint2 implements Point2 {
x = 1;
y = 2;
}

type PartialPoint = { x: number; } | { y: number; };

// 错误:不能实现联合类型
class SomePartialPoint implements PartialPoint {
x = 1;
y = 2;
}

5. 声明合并

接口可以多次定义,其成员会被合并,而类型别名不能。

1
2
3
4
5
// 这两个声明会合并为:interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };

核心代码

原始类型别名

1
2
3
type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;

元组类型

1
type row = [colOne: number, colTwo: string];

函数类型

1
2
3
4
5
6
7
// 通过类型别名
type Sum = (x: number, y: number) => number;

// 通过接口
interface Sum {
(x: number, y: number): number;
}

联合类型

1
2
3
4
5
type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';

// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;

映射类型

1
2
3
4
5
6
7
8
9
10
11
type Fruit = 'apple' | 'orange' | 'banana';

type FruitCount = {
[key in Fruit]: number;
}

const fruits: FruitCount = {
apple: 2,
orange: 3,
banana: 4
};

最佳实践

使用类型别名的场景

  • 定义原始类型的别名。
  • 定义元组类型。
  • 定义函数类型。
  • 定义联合类型。
  • 利用映射类型。
  • 进行泛型转换。
  • 创建类型别名以简化复杂类型。
  • 捕获未知对象的类型。

使用接口的场景

  • 定义对象类型,除非需要使用类型别名的特定功能。
  • 利用声明合并,例如扩展第三方库的类型。
  • 实现多态性,使代码更易读,尤其是在团队协作或开源项目中。

常见问题

索引差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface MyInterface {
foobar: string;
}

type MyType = {
foobar: string;
};

const exampleInterface: MyInterface = { foobar: 'hello world' };
const exampleType: MyType = { foobar: 'hello world' };

let record: Record<string, string> = {};

record = exampleType; // 编译通过
record = exampleInterface; // 错误:缺少索引签名

评估差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 当FirstLevelType是接口时
interface FirstLevelType<A, Z> {
_: "typeCheck";
};

type TestWrapperType<T, U> = FirstLevelType<T, U>;


const a: TestWrapperType<{ cat: string }, { dog: number }> = {
_: "typeCheck",
};

// { cat: string; }
type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _>
? T
: "not extended";

// 当FirstLevelType是类型别名时
type FirstLevelType<A, Z> = {
_: "typeCheck";
};

type TestWrapperType<T, U> = FirstLevelType<T, U>;


const a: TestWrapperType<{ cat: string }, { dog: number }> = {
_: "typeCheck",
};

// unknown
type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _>
? T
: "not extended";

递归类型定义问题

1
2
3
4
5
6
7
8
9
10
11
type Boxed<T> = { value: T };

type BoxedString = Boxed<BoxedString> | string;
// 错误:类型别名 'BoxedString' 循环引用自身。

// 使用接口则可以正常工作
interface Boxed<T> {
value: T;
}

type BoxedString = Boxed<BoxedString> | string;

综上所述,在TypeScript中,接口和类型别名各有其优势和适用场景。了解它们的区别可以帮助我们更准确地定义类型,提高代码的可读性和可维护性。


TypeScript中接口与类型别名的对比
https://119291.xyz/posts/2025-05-13.typescript-interfaces-vs-types/
作者
ww
发布于
2025年5月13日
许可协议