【TypeScript】interface 与 type 的区别
笔者有话要讲
我们在使用 TypeScript 时,经常会用到这两个声明 interface
和 type
,我一直存在一个疑惑,这两个究竟有什么区别,各自又该用在什么场景。
恰巧关注的公众号——《大迁世界》推送了一篇文章——《使用 TypeScript 常见困惑:interface 和 type 的区别是什么?》,感觉文章排版有些错乱,看着属实难受。
所幸本人还是能看懂一点点英文的(指有道词典),所以干脆找到原文直接自己翻(run)译(se)好了,顺便还加了一点点补丁(patch)。
typescript 版本:4.4.4
一、概念
1.1. 类型 Types
我们知道 TypeScript 中有许多类型,有基础的类型如 string, number, 和 boolean,也有特殊的如数组 Arrays、元组 Turple 等等。这些类型使用起来非常简单,比如
1 | const ExampleStr:string = 'my name'; |
1.2. 联合类型 Union Types
理所当然的,一个变量可能不止一种类型,这种情况在实际开发中并不少见。举个简单的例子,在一些业务中,我们会先声明一个变量,然后通过各种判断给这个变量赋值:
1 | let target = null; |
在上例中,target 的值开始时是 null 类型,程序执行完毕后是 number 类型。这种情况我们就可以使用组合类型,表明 target 可以是 null 也可以是 number。
1 | type ObjType = { a: number }; |
1.2.1. 基本类型联合
基本类型联合,允许访问任意联合成员类型,如
1 | let target = string | number; |
1.2.2. 对象类型联合
对象类型联合,只能访问联合中共同的成员,如
1 | interface Women { |
Note: 有一个值得注意的点,如果我们使用字面量或者整体赋值的方式,而不是 obj.x=xx
的方式给变量赋值,那么赋的这个值至少应包含联合中一个成员的所有属性/方法:
1 | interface A { |
即使通过这种方式赋值,我们也不可以访问对象联合类型的非共有属性/方法:
1 | let inst: A | B = { |
1.3. 交叉类型 Intersection Types
与联合类型取交集不同,交叉类型取的并集,即,交叉类型允许访问成员类型的所有属性。
自然,交叉类型只能是两个对象类型的交叉,毕竟基本类型也没办法交叉不是。
1 | interface Women { |
交叉类型对象的赋值必须包含该交叉类型指定的所有属性/方法。
1 | interface A { |
1.4. 类型别名 Type Aliases
举个栗子
1 | const arr1: [string, number] = ['a', 1]; |
在实际的开发过程中,我们或多或少会遇到上面这种写法才能实现的逻辑。这种变量只有一个两个的话还好,如果有大量相同类型的变量,比如
1 | const obj1: { a: string, b: number } = { a: 'a', b: 2 }; |
也许写起来还不会太麻烦(ctrl+c/ctrl+v 就好),但是看上去令人强迫症(指“优化”代码)发作。
TypeScript 自然也提供了解决方案,那就是 类型别名 Type Aliases。
1 | type CommonType = { a: string, b: number }; |
类型别名允许我们通过一个名称来使用一些或简单(正常会用在基本类型上么?)或复杂的类型,这样无论是编写难易度还是代码美观程度上,都比之前那种写法好得多。
1.5. 接口 Interfaces
官方文档中的解释:An interface declaration is another way to name an object type
。interface 是声明对象类型的一种方式。
举个栗子
1 | type CommonType = { a: string, b: number }; |
二、相同点 type and interface
都可以声明对象类型;
1
2
3
4
5
6
7
8interface PointA {
x: number;
y: number;
}
type PointB = {
x: number;
y: number;
}都可以被实现(implements);
1
2
3
4
5
6
7
8
9
10
11interface InterfaceA {
name: string;
};
class PersonA implements InterfaceA {
name = 'Kaze';
}
type TypeB = { age: number };
class PersonB implements TypeB {
age = 17;
}都可以联合、交叉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type A = {
name: string;
age: number;
}
type B = {
name: string;
isMale: boolean;
}
let Person1: A | B;
let Person2: A & B;
// interface 也可以
interface A {
name: string;
age: number;
}
interface B {
name: string;
isMale: boolean;
}
let Person1: A | B;
let Person2: A & B;
三、不同点 type vs interface
声明语法不同
1
2
3
4
5
6
7
8type A = { name: string };
type B = [string, number];
type C = string;
interface D {
name: string;
age: number;
}重复声明效果不同
interface 重复声明会合并,同名替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14interface Person {
name: string;
gender: string;
}
interface Person {
age: number;
gender: number;
}
// 等同于
interface Person {
name: string;
age: number;
gender: number;
}type 重复声明会报错
1
2
3
4
5
6
7type One = {
name: string;
}
type One = {
age: number;
}
// 报错:Duplicate identifier "One"
interface 只针对对象类型,而 type 则没有此限制(毕竟只是个别名而已)。举个栗子
1 | type ArrType = [string, number]; |
你甚至可以
1 | interface Person { |
interface 和 type 都可以实现继承,甚至 interface 和 type 之间也可以相互继承,但语法有所区别。
Interface 继承 interface:使用 extends 关键字
1
2
3
4
5
6interface A {
name: string;
}
interface B extends A {
age: number;
}interface 继承 type:同样使用 extends 关键字
1
2
3
4
5
6type A = {
name: string;
}
interface B extends A {
age: number;
}type 继承 type:交叉类型
1
2
3
4
5
6type A = {
name: string;
}
type B = A & {
age: number;
}type 继承 interface:交叉类型
1
2
3
4
5
6interface A {
name: string;
}
type B = A & {
age: number;
}
如何使用
在大多数情况下,你可以根据个人偏好进行选择,TypeScript 会告诉你是否需要其他类型的声明。[3]
如果您想使用启发式方法(heuristic),除部分需要使用 type 特性的情况外,请使用接口 interface。[3]
对于库中的公共 API 定义或者第三方类型定义,应使用接口(声明合并功能)。
除此之外,我们可以随意使用,但在整个项目中应保持一致性。
这就是 TypeScript 中关于接口 vs 类型的所有知识。 希望这篇文章能帮到你,如果对你有所帮助的话,请分享给你的朋友!
参考文献:
[1] TypeScript 官方文档#type-aliases
[2] TypeScript 官方文档#interfaces
[3] TypeScript 官方文档#Differences Between Type Aliases and Interfaces
[4] 使用 TypeScript 常见困惑:interface 和 type 的区别是什么?
[5] [SARANSH KATARIA]TypeScript: the difference between interface and type