
TypeScript 2.7中的Interface vs Type别名
人们经常问我,使用类型和接口在TypeScript中定义编译时间类型有什么区别。
我以前要做的第一件事就是将它们指向TypeScript手册…
不幸的是,大多数情况下,他们找不到所需的(隐藏在“高级类型”部分中)。
现在您无需再进行任何查找,此文章是有关何时使用接口与类型别名的描述/样式指南。
官方文件说明:
“类型别名可以起到类似接口的作用,但是存在一些细微的差异。”
没错!
有什么区别?
1. “不同之处在于,接口创建了一个新名称,该名称随处可见。类型别名不会创建新名称-例如,错误消息不会使用别名。”
这是不对的! (自TypeScript 2.1起)
让我们通过接口和类型别名为Point定义编译时类型,以及getRectangleSquare函数的2种实现,它将使用接口和类型别名来进行参数类型注释。
分别定义接口Point,类型别名Point:
interface PointInterface {
x: number
y: number
}
type PointType = {
x: number
y: number
}
使用接口和类型别名的getRectangleArea函数:
const getRectangleAreaInterface = (args: PointInterface) => args.x * args.y
const getRectangleAreaAliased = (args: PointType) => args.x * args.y
因此,两者的错误相同:
// TS Error:
// Interface:
Argument of type '{ x: number; }' is not assignable to parameter of type 'PointInterface'. Property 'y' is missing in type '{ x: number; }'.
// Type alias:
Argument of type '{ x: number; }' is not assignable to parameter of type 'PointType'. Property 'y' is missing in type '{ x: number; }'.
2. “第二个更重要的区别是类型别名不能从其扩展或实现”
这次又是不对的!
我们可以使用别名类型扩展接口:
interface ThreeDimensions extends PointType {
z: number
}
或者使用类型别名来实现Class约束
class Rectangle implements PointType {
x = 2
y = 4
}
或者使用通过类型扩展的接口来实现Class约束
class RectanglePrism implements ThreeDimensions {
x = 2
y =
z = 4
}
我们还可以结合使用类型别名和接口来实现Class约束
interface Shape {
area(): number
}
type Perimeter = {
perimiter(): number
}
class Rectangle implements PointType, Shape, Perimeter {
x = 2
y = 3
area() {
return this.x * this.y
}
perimeter() {
return 2 * (this.x + this.y)
}
}
3. “类型别名不能扩展/实现其他类型”
再一次不对!
您可以通过交集运算符&使用接口或任何其他TypeScript有效类型(具有Dictionary / JS Object的Shape,因此是非基本类型等)来进行类型别名扩展。
class Point {
x: number
y: number
}
interface Shape {
area(): number
}
type Perimeter = {
perimiter(): number
}
type RectangleShape = Shape & Perimeter & Point
class Rectangle implements RectangleShape {
x = 2
y = 3
area() {
return this.x * this.y
}
perimeter() {
return 2 * (this.x + this.y)
}
}
我们还可以将映射类型用于接口和类型别名的各种转换。 让我们通过部分映射类型将“Shape”和“Perimeter”设为可选:
class Point {
x: number
y: number
}
interface Shape {
area(): number
}
type Perimeter = {
perimiter(): number
}
type RectangleShape = Partial<Shape & Perimeter> & Point
// 和接口一样
// 接口RectangleShape 继承 Partial<Shape & Perimeter>,Point {}
class Rectangle implements RectangleShape {
x = 2
y = 3
}
弱类型检测也可以正常工作:

具有类型别名和接口的混合类型
您可能偶尔会想定义一个既具有功能又具有附加属性的对象。
我们在这里谈论的是定义一个函数的类型(可调用对象)和该函数的静态属性。
与第三方JavaScript进行交互时,也可能会看到此模式,以完全描述类型的形状。
混合类型定义和实现:
interface Counter {
// 回调函数部分
(start: number): string
// 静态属性
interval: number
reset(): void
}
const getCounter = () => {
const counter = ((start: number) => {}) as Counter
counter.interval = 123
counter.reset = () => {}
return counter
}
const callable = getCounter()
callable(10)
callable.reset()
callable.interval = 5.0
它与类型别名效果一样!
type Counter = {
// 回调函数部分
(start: number): string
// 静态属性
interval: number
reset(): void
}
但是有一个非常细微的差异。您将在IDE中获得特定的形状类型,而不是引用Counter type。

通常的好主意/做法是将混合定义分为两部分:
- 可调用对象(函数)类型别名
/* 通过类型别名定义 */
type CounterFn = (start: number) => string
/* 通过接口定义 */
interface CounterFn {
(start: number): string
}
- 静态特性对象形状
/* 通过类型别名定义 */
type CounterStatic = {
interval: number
reset(): void
}
/* 通过接口定义 */
interface CounterStatic {
interval: number
reset(): void
}
最终Counter 类型:
/* 通过类型别名定义 */
type Counter = CounterFn & CounterStatic
/* 通过接口定义 */
interface Counter extends CounterFn, CounterStatic {}
那么类型别名和接口又有什么区别 ?
1. 类型别名union不能用于`implements`
这将触发编译错误:

使 类型别名union 用法有意义并且可行的方式是通过字面量定义对象。因此,以下操作是有效的,并且会产生编译错误,因为我们的对象必须定义perimeter() 或area() 方法之一,或两者都定义:

2. 如果在类型定义中使用联合运算符,则不能在具有类型别名的接口上使用 extends

再次,类似于类 implements 的用法,接口是一个“静态”蓝图-它不能以一种或另一种形状存在,因此不能通过联合类型合并对其进行继承。
3. 声明合并不适用于类型别名
虽然声明合并适用于接口,但使用类型别名失败。
我所说的声明合并是什么意思:
您可以多次定义相同的接口,并且其定义将合并为一个:
interface Box {
height: number
width: number
}
interface Box {
scale: number
}
const box:Box = { height: 5, width: 6, scale: 10 }
这不适用于类型别名,因为类型是唯一的类型实体(对于全局作用域或模块作用域):

当我们为未使用TypeScript编写的库编写第三方环境类型定义时,通过接口进行的声明合并非常重要,因此,如果缺少某些定义,则消费者可以选择扩展它们。
如果我们的库是用TypeScript编写的,并且环境类型定义是自动生成的,则同样适用。
这是唯一的用例,您绝对应该始终使用接口而不是类型别名!
React Props和State使用什么?
通常,使用你想用的(类型别名/接口)只是保持一致,但就个人而言,我建议使用类型别名:
- type Props = {} 写起来剪短一些
- 语法一致(对于可能的类型交集,不是具有类型别名的mixin接口)
// BAD
interface Props extends OwnProps, InjectedProps, StoreProps {}
type OwnProps = {...}
type StoreProps = {...}
// GOOD
type Props = OwnProps & InjectedProps & StoreProps
type OwnProps = {...}
type StoreProps = {...}
- 您的公共组件Props / State实现无法进行猴子修补,因此,组件的使用者永远不需要利用接口声明合并。为了扩展,有明确定义的模式,例如HOC等。
最后
在本文中,我们了解了TypeScript中的接口和类型别名之间的区别。
涵盖了这一点,我们得出了一个结论,即在特定情况下应使用哪种定义编译期类型的方法。
让我们回顾一下:
- 类型别名可以起到类似接口的作用,但是有3个重要的区别(联合类型,声明合并)
- 使用适合您和您的团队的方式,但要保持一致
- 在创作库或第三方环境类型定义时,始终使用接口作为公共API的定义
- 考虑将 type 用于您的React Component Props和State