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; } 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';
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
| interface FirstLevelType<A, Z> { _: "typeCheck"; };
type TestWrapperType<T, U> = FirstLevelType<T, U>;
const a: TestWrapperType<{ cat: string }, { dog: number }> = { _: "typeCheck", };
type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _> ? T : "not extended";
type FirstLevelType<A, Z> = { _: "typeCheck"; };
type TestWrapperType<T, U> = FirstLevelType<T, U>;
const a: TestWrapperType<{ cat: string }, { dog: number }> = { _: "typeCheck", };
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;
interface Boxed<T> { value: T; }
type BoxedString = Boxed<BoxedString> | string;
|
综上所述,在TypeScript中,接口和类型别名各有其优势和适用场景。了解它们的区别可以帮助我们更准确地定义类型,提高代码的可读性和可维护性。