TypeScript中的type和interface的区别?你清楚吗?

173 阅读5分钟

在 TypeScript 中,type 和 interface 都可以用来定义和描述数据的结构,虽然它们的写法很相似,但在某些方面也存在一些区别。

定义:

1. 接口(interface):

用于定义对象的结构和行为。

Vue 3 中的 App 对象就是使用 interface 来定义的:

export interface App<HostElement = any> {
  version: string
  config: AppConfig
  use(plugin: Plugin, ...options: any[]): this
  mixin(mixin: ComponentOptions): this
  component(name: string): Component | undefined // Getter
  component(name: string, component: Component): this // Setter
  directive(name: string): Directive | undefined
  directive(name: string, directive: Directive): this
}

2. 类型别名(type):

类型别名只是给类型起一个新名字。它并不会创建一个新的类型,只是一个别名而已。

对命名基本类型或联合类型等非对象类型时非常有用!

例如:

type MyNumber = number; //基本类型
type StringOrNumber = string | number; // 联合类型
type Text = string | string[]; // 联合类型
type Point = [number, number]; // 元组类型
type Callback = (data: string) => void; // 函数类型

相同点

1. 类型别名和接口都支持扩展(继承)

- 类型别名的扩展是通过交叉操作符(&)来实现的

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 属性。

- 接口的扩展是通过 extends 来实现的

interface Shape {
  color: string;
  getArea(): number;
}
// Circle 接口继承了 Shape 接口中的属性和方法
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. 类型别名和接口都可以用来描述对象或函数

- 类型别名定义对象和函数

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

const point2D: Point2D = {
  x: 100,
  y: 200,
};
console.log(point2D.x, point2D.y); // 输出:100 200

type MathOperation = (x: number, y: number) => number; 
const add: MathOperation = (x, y) => x + y; 

console.log(add(100, 200)); // 输出:300

- 接口定义对象和函数

interface Point2D {
  x: number;
  y: number;
}

const point2D: Point2D = {
  x: 100,
  y: 200,
};

console.log(point2D.x, point2D.y); // 输出:100 200

interface MathOperation {(x: number, y: number): number;}
const add: MathOperation = (x, y) => x + y;

console.log(add(100, 200)); // 输出:300

不同点

1.接口使用 interface 关键字定义,类型别名使用 type 关键字定义

接口的定义:

interface Person {
  name: string;
  age: number;
}

类型别名的定义:

type Person = {
  name: string;
  age: number;
}

type MyType = string | number | boolean;

2. 同名的接口会自动合并,而同名的类型别名不行

同名接口:

interface Person {
  id: number;
  name: string;
}

interface Person {
  age: number;
}

// 上方两个 Person 接口 等同于 下方一个 Person 接口
interface Person { 
    id: number; 
    name: string; 
    age: number;
}

// 使用 Person 接口
const person: Person = { 
    id: 1, name: 
    'Echo', 
    age: 26, 
}

同名类型别名:

type Person = {
  id: number;
  name: string;
}

type Person = {
  age: number;
}

// 编译错误:标识符“Person”重复。ts(2300)

3. 类型别名可以用于基本类型、联合类型和元组类型的定义,而接口只能用于描述对象的形状和行为。

// 基本类型
type MyString = string;
type MyNumber = number;
type MyBoolean = boolean;

const username: MyString = 'Echo';
const age: MyNumber = 26;
const isMale: MyBoolean = true;

// 联合类型
type MyType = string | number | boolean;

const username: MyString = 'Echo';
const age: MyNumber = 26;
const isMale: MyBoolean = true;

// 元组类型
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),用来约束类的结构和行为,而类型别名不能

注意:通过类实现接口,类必须满足接口中定义的属性和方法。

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关键字来继承其他接口,实现接口的复用

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 接口,必须满足接口中定义的所有属性和方法。

什么时候用接口,什么时候用类型别名?

使用接口的一些场景:

  • 描述对象的形状和结构:如果需要定义一个具有特定属性和方法的对象结构,接口是更适合的选择。接口可以明确指定每个属性的类型和方法的签名。
  • 类的实现。
  • 继承其它接口。
  • 需要利用接口自动合并特性的时候
  • 定义对象类型

使用类型别名的一些场景:

  • 为现有类型起别名
  • 定义联合类型、交叉类型或其它复杂类型
  • 表示函数类型