TypeScript常见困惑🤔,type和interface的区别你弄清楚没?

在 TypeScript 中,typeinterface 都可以用来定义和描述数据的结构,在面试中我们也经常会被问到,如果你还不知道它们之间有什么区别的话,看完本文也许你就懂了。

虽然它们的写法很相似,但在某些方面也存在一些区别。

定义:

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)则适用于给已有的类型起别名,并创建或组合复杂的类型。在开发过程中,我们需要根据实际情况去选择适合的定义方式。

相关推荐
Jiaberrr1 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy2 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白2 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、2 小时前
Web Worker 简单使用
前端
web_learning_3212 小时前
信息收集常用指令
前端·搜索引擎
tabzzz2 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百2 小时前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao2 小时前
自动化测试常用函数
前端·css·html5
码爸2 小时前
flink doris批量sink
java·前端·flink
深情废杨杨3 小时前
前端vue-父传子
前端·javascript·vue.js