在 TypeScript 中,type
和 interface
都可以用来定义和描述数据的结构,在面试中我们也经常会被问到,如果你还不知道它们之间有什么区别的话,看完本文也许你就懂了。
虽然它们的写法很相似,但在某些方面也存在一些区别。
定义:
1. 接口(interface):
用于定义对象的结构和行为。
2. 类型别名(type):
类型别名只是给类型起一个新名字。它并不会创建一个新的类型,只是一个别名而已。
相同点:
1. 类型别名和接口都支持扩展。
1.1. 类型别名的扩展是通过交叉操作符(&)来实现的。
typescript
type Person = {
name: string;
age: number;
};
type User = Person & {
gender: string;
city: string;
};
const user: User = {
name: 'Echo',
age: 26,
gender: 'Male',
city: 'Guang Zhou',
};
上面这段代码中,我们定义了两个类型别名 Person 和 User,其中,类型 User 通过交叉类型操作符(&)将Person 与 gender 和 city 组合成了新的类型。最后,我们创建了一个 user 对象,它同时具有 name、age、gender 和 city 属性。
1.2. 接口的扩展是通过 extends 来实现的。
typescript
interface Shape {
color: string;
getArea(): number;
}
interface Circle extends Shape {
radius: number;
}
class CircleImpl implements Circle {
color: string;
radius: number;
constructor(color: string, radius: number) {
this.color = color;
this.radius = radius;
}
getArea(): number {
return Math.PI * this.radius * this.radius;
}
}
const circle: Circle = new CircleImpl('red', 10);
console.log(circle.getArea()); // 输出:314.1592653589793
上面这段代码中,我们定义了一个 Shape 接口,它包含了 color 属性和 getArea 方法。然后我们通过 extends 关键字将 Circle 接口继承了 Shape 接口,并添加了 radius 属性。接着,我们创建了 CircleImpl 类,实现了 Circle 接口中的所有属性和方法。最后,我们创建了一个 circle 对象,可以调用相应的方法。
2. 类型别名和接口都可以定义对象和函数。
2.1. 使用接口定义对象
typescript
interface Point2D {
x: number;
y: number;
}
const point2D: Point2D = {
x: 100,
y: 200,
};
console.log(point2D.x, point2D.y); // 输出:100 200
2.2. 使用类型别名定义对象
typescript
type Point2D = {
x: number;
y: number;
}
const point2D: Point2D = {
x: 100,
y: 200,
};
console.log(point2D.x, point2D.y); // 输出:100 200
2.3. 使用接口定义函数
typescript
interface MathOperation {
(x: number, y: number): number;
}
const add: MathOperation = (x, y) => x + y;
console.log(add(100, 200)); // 输出:300
2.4. 使用类型别名定义函数
typescript
type MathOperation = (x: number, y: number) => number;
const add: MathOperation = (x, y) => x + y;
console.log(add(100, 200)); // 输出:300
3. 不同点:
1. 接口使用 interface 关键字定义,类型别名使用 type 关键字定义。
接口的定义:
typescript
interface Person {
name: string;
age: number;
}
类型别名的定义:
typescript
type Person = {
name: string;
age: number;
}
type MyType = string | number | boolean;
2. 同名的接口会自动合并,而同名的类型别名不行。
当声明了多个同名的接口时,TypeScript会将它们的属性、方法等成员进行合并,并形成一个包含了所有成员的合并接口。合并后的接口与在单独声明时的接口使用方式相同,可以用于约束对象的形状和行为。
typescript
interface Person {
id: number;
name: string;
}
interface Person {
age: number;
}
const person: Person = {
id: 1,
name: 'Echo',
age: 26,
}
在上面这段代码中,我们分别声明了两个同名的 Person 接口,第一个接口 Person 中有 id 和 name 属性,第二个接口中有 age 属性。由于是在同一个作用域中声明的,并且名称相同,TypeScript 会将它们自动合并成一个接口:
typescript
interface Person {
id: number;
name: string;
age: number;
}
这样,我们可以使用合并后的 Person 接口来约束 person 对象,它需要同时具有 id、name 和 age 属性。
而如果在同一个作用域中定义了多个同名的类型别名,TypeScript 会报错:提示多次定义了同一个类型别名。这是因为类型别名的定义是直接为现有类型起一个别名,无法进行合并。
typescript
type Person = {
id: number;
name: string;
}
type Person = {
age: number;
}
// 编译错误:标识符"Person"重复。ts(2300)
3. 类型别名可以用于基本类型、联合类型和元组类型的定义,而接口只能用于描述对象的形状和行为。
使用 type 定义基本类型别名
类型别名可以用于为基本类型定义别名,例如字符串、数字、布尔值等。
typescript
type MyString = string;
type MyNumber = number;
type MyBoolean = boolean;
const username: MyString = 'Echo';
const age: MyNumber = 26;
const isMale: MyBoolean = true;
使用 type 定义联合类型别名
类型别名可以将多个类型组合成联合类型,表示一个值可以是多种类型中的一种。
typescript
type MyType = string | number | boolean;
const username: MyString = 'Echo';
const age: MyNumber = 26;
const isMale: MyBoolean = true;
使用 type 定义元组类型别名
类型别名可以用于定义元组类型,表示一个固定长度和特定类型的数组。
typescript
type Point2D = [number, number];
const p1: Point2D = [1, 2]; // 有效的元组
const p2: Point2D = [3, 4]; // 有效的元组
const p3: Point2D = [5, 6, 7]; // 编译错误:不能将类型"[number, number, number]"分配给类型"Point2D"。源具有 3 个元素,但目标仅允许 2 个。ts(2322)
const p4: Point2D = [8]; // 编译错误:不能将类型"[number]"分配给类型"Point2D"。源具有 1 个元素,但目标需要 2 个。
4. 接口可以被类实现(implements),用来约束类的结构和行为,而类型别名不能。
接口可以被类实现,用来约束类的结构和行为。通过实现接口,类必须满足接口中定义的属性和方法要求。
typescript
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound(): void {
console.log('Woof!');
}
}
const dog = new Dog('Buddy');
dog.makeSound(); // 输出: 'Woof!'
在上面这段代码中,Animal 接口定义了 name 属性和 makeSound 方法,类 Dog 通过 implements 关键字实现了 Animal 接口。这意味着类 Dog 必须具有 name 属性和 makeSound 方法,并且实现 makeSound 方法的具体逻辑。
5. 接口还可以通过extends关键字来继承其他接口,实现接口的复用。
接口之间可以通过 extends 关键字来建立继承关系,实现接口的复用。子接口会继承父接口的属性和方法,并且可以在子接口中添加额外的属性和方法。
typescript
interface Shape {
color: string;
getArea(): number;
}
interface Rectangle extends Shape {
width: number;
height: number;
}
class Square implements Rectangle {
color: string;
width: number;
height: number;
constructor(color: string, width: number) {
this.color = color;
this.width = width;
this.height = width;
}
getArea(): number {
return this.width * this.height;
}
}
const square = new Square('red', 5);
console.log(square.getArea()); // 输出: 25
在上面这段代码中,Shape 接口定义了 color 属性和 getArea 方法,Rectangle 接口继承了 Shape 接口,并添加了 width 和 height 属性。类 Square 实现了 Rectangle 接口,必须满足接口中定义的所有属性和方法。
4. 什么时候用接口,什么时候用类型别名?
使用接口的一些场景:
- 描述对象的形状和结构:如果需要定义一个具有特定属性和方法的对象结构,接口是更适合的选择。接口可以明确指定每个属性的类型和方法的签名。
- 类的实现。
- 继承其它接口。
使用类型别名的一些场景:
- 为现有类型起别名。
- 定义联合类型、交叉类型或其它复杂类型。
- 表示函数类型
5. 总结:
接口(interface)适用于描述对象的结构和行为,而类型别名(type)则适用于给已有的类型起别名,并创建或组合复杂的类型。在开发过程中,我们需要根据实际情况去选择适合的定义方式。