这些语句(界面与类型)之间有什么区别?
interface X {
a: number
b: string
}
type X = {
a: number
b: string
};
答案 0 :(得分:388)
根据TypeScript Language Specification:
与总是引入命名对象类型的接口声明不同,类型别名声明可以为任何类型引入名称,包括基本类型,联合类型和交集类型。
规范接着提到:
接口类型与对象类型的别名类型有许多相似之处 文字,但由于界面类型提供了更多的功能 通常喜欢键入别名。例如,接口类型
interface Point { x: number; y: number; }
可以写为类型别名
type Point = { x: number; y: number; };
但是,这样做意味着丢失了以下功能:
接口可以在extends或implements子句中命名,但是对象类型文字的类型别名不能不再为真,因为TS 2.7。- 接口可以有多个合并声明,但对象类型文字的类型别名不能。
答案 1 :(得分:227)
当前答案和official documentation已过时。对于TypeScript的新手来说,没有示例就不清楚使用的术语。下面是最新的差异列表。
两者都可以用来描述对象的形状或功能签名。但是语法不同。
界面
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
键入别名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
与接口不同,类型别名也可以用于其他类型,例如基元,并集和元组。
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
两者都可以扩展,但是语法也有所不同。此外,请注意,接口和类型别名不是互斥的。接口可以扩展类型别名,反之亦然。
界面扩展界面
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
类型别名扩展了类型别名
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
接口扩展了类型别名
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
类型别名扩展了界面
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
一个类可以以完全相同的方式实现接口或类型别名。但是请注意,类和接口被视为静态蓝图。因此,他们不能实现/扩展名为联合类型的类型别名。
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x: 1;
y: 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x: 1;
y: 2;
}
type PartialPoint = { x: number; } | { y: number; };
// FIXME: can not implement a union type
class SomePartialPoint implements PartialPoint {
x: 1;
y: 2;
}
与类型别名不同,接口可以定义多次,并且将被视为单个接口(所有声明的成员都将被合并)。
// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
答案 2 :(得分:56)
对于typescrpt 版本:4.3.4
我在下面描述的个人习惯是这样的:
<块引用>总是喜欢 interface
而不是 type
。
何时使用 type
:
type
type
type
type
type
type
何时使用 interface
:
interface
的所有对象类型使用 type
(见上文)interface
。type
和 interface
之间最容易看到的区别是只有 type
可用于为原语设置别名:
type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;
这些示例都无法通过接口实现。
? 为原始值提供类型别名时,请使用 type
关键字。
元组只能通过 type
关键字输入:
type row = [colOne: number, colTwo: string];
? 在为元组提供类型时使用 type
关键字。
函数可以通过 type
和 interface
关键字输入:
// via type
type Sum = (x: number, y: number) => number;
// via interface
interface Sum {
(x: number, y: number): number;
}
由于两种方式都可以达到相同的效果,因此规则是在这些场景中使用 type
,因为它更容易阅读(并且不那么冗长)。
? 在定义函数类型时使用 type
。
联合类型只能通过 type
关键字实现:
type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';
// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;
? 定义联合类型时,使用 type
关键字
javascript 中的对象是键/值映射,“对象类型”是打字稿键入这些键/值映射的方式。在为对象提供类型时,interface
和 type
都可以使用,因为原始问题已经清楚了。那么对于对象类型,您什么时候使用 type
与 interface
?
通过类型和组合,我可以做这样的事情:
interface NumLogger {
log: (val: number) => void;
}
type StrAndNumLogger = NumLogger & {
log: (val: string) => void;
}
const logger: StrAndNumLogger = {
log: (val: string | number) => console.log(val)
}
logger.log(1)
logger.log('hi')
打字稿非常高兴。如果我尝试使用接口扩展它呢:
interface StrAndNumLogger extends NumLogger {
log: (val: string) => void;
};
StrAndNumLogger
的声明给了我一个 error:
Interface 'StrAndNumLogger' incorrectly extends interface 'NumLogger'
对于接口,子类型必须与超类型中声明的类型完全匹配,否则 TS 会抛出类似上面的错误。
? 在尝试重载对象类型中的函数时,最好使用 type
关键字。
打字稿中接口与类型区分开来的关键方面是它们可以在声明后使用新功能进行扩展。当您想要扩展从节点模块导出的类型时,会出现此功能的常见用例。例如,@types/jest
导出在使用 jest 库时可以使用的类型。但是,jest 还允许使用新函数扩展主要的 jest
类型。例如,我可以添加这样的自定义测试:
jest.timedTest = async (testName, wrappedTest, timeout) =>
test(
testName,
async () => {
const start = Date.now();
await wrappedTest(mockTrack);
const end = Date.now();
console.log(`elapsed time in ms: ${end - start}`);
},
timeout
);
然后我可以这样使用它:
test.timedTest('this is my custom test', () => {
expect(true).toBe(true);
});
现在,一旦测试完成,该测试所用的时间将打印到控制台。伟大的!只有一个问题 - typescript 不知道我添加了 timedTest
函数,所以它会在编辑器中抛出一个错误(代码可以正常运行,但 TS 会生气)。
为了解决这个问题,我需要告诉 TS 在 jest 已经可用的现有类型之上有一个新类型。为此,我可以这样做:
declare namespace jest {
interface It {
timedTest: (name: string, fn: (mockTrack: Mock) => any, timeout?: number) => void;
}
}
由于接口的工作方式,此类型声明将与从 @types/jest
导出的类型声明合并。所以我不只是重新声明 jest.It
;我使用新函数扩展了 jest.It
,以便 TS 现在知道我的自定义测试函数。
type
关键字无法实现这种类型的事情。如果 @types/jest
用 type
关键字声明了它们的类型,我将无法用我自己的自定义类型扩展这些类型,因此没有什么好方法让 TS 满意我的新功能。这个 interface
关键字独有的过程称为 declaration merging。
声明合并也可以像这样在本地进行:
interface Person {
name: string;
}
interface Person {
age: number;
}
// no error
const person: Person = {
name: 'Mark',
age: 25
};
如果我对 type
关键字执行上述完全相同的操作,则会收到错误消息,因为类型无法重新声明/合并。在现实世界中,javascript 对象很像这个 interface
示例;它们可以在运行时使用新字段动态更新。
? 因为接口声明可以合并,所以接口比类型更准确地表示 javascript 对象的动态性质,因此它们应该是首选。
使用 type
关键字,我可以像这样利用 mapped types:
type Fruit = 'apple' | 'orange' | 'banana';
type FruitCount = {
[key in Fruit]: number;
}
const fruits: FruitCount = {
apple: 2,
orange: 3,
banana: 4
};
这不能用接口来完成:
type Fruit = 'apple' | 'orange' | 'banana';
// ERROR:
interface FruitCount {
[key in Fruit]: number;
}
? 当需要利用映射类型时,使用 type
关键字
大多数时候,对象类型的简单类型别名的作用与接口非常相似。
interface Foo { prop: string }
type Bar = { prop: string };
但是,一旦您需要组合两个或多个类型,您就可以选择使用接口扩展这些类型,或者将它们交叉在一个类型别名中,这就是差异开始变得重要的时候。
接口创建了一个单一的平面对象类型来检测属性冲突,这通常很重要!另一方面,交叉点只是递归地合并属性,在某些情况下永远不会产生。界面也始终显示得更好,而交叉点的类型别名不能在其他交叉点的一部分中显示。接口之间的类型关系也被缓存,而不是作为一个整体的交集类型。最后一个值得注意的区别是,在检查目标交叉点类型时,在检查“有效”/“扁平”类型之前,先检查每个成分。
出于这个原因,建议使用接口/扩展扩展类型而不是创建交叉类型。
更多关于typescript wiki。
答案 3 :(得分:36)
答案 4 :(得分:23)
https://www.typescriptlang.org/docs/handbook/advanced-types.html
一个区别是接口创建了一个在任何地方都使用的新名称。类型别名不会创建新名称 - 例如,错误消息将不使用别名。
答案 5 :(得分:21)
type
?通用转换
将多种类型转换为单个通用类型时,请使用type
。
示例:
type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T
类型别名
我们可以使用type
为难以理解的长或复杂类型创建别名,并且不方便一次又一次地键入。
示例:
type Primitive = number | string | boolean | null | undefined
创建这样的别名可使代码更加简洁和可读。
捕获类型
当类型未知时,使用type
捕获对象的类型。
示例:
const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit
在这里,我们得到orange
的未知类型,将其称为Fruit
,然后使用Fruit
创建一个新的类型安全对象apple
。
interface
?多态
interface
是用于实现数据形状的协定。使用该接口可以清楚地表明该对象旨在实现并用作有关如何使用该对象的合同。
示例:
interface Bird {
size: number
fly(): void
sleep(): void
}
class Hummingbird implements Bird { ... }
class Bellbird implements Bird { ... }
尽管您可以使用type
来实现此目的,但Typescript被更多地视为一种面向对象的语言,而interface
在面向对象的语言中占有特殊的位置。在团队环境中工作或为开源社区做出贡献时,使用interface
读取代码会更容易。来自其他面向对象语言的新程序员也很容易。
官方打字稿documentation也说:
......我们建议尽可能在
interface
别名上使用type
。
这还表明type
比创建类型本身更适合用于创建类型别名。
声明合并
您可以使用interface
的声明合并功能为已声明的interface
添加新的属性和方法。这对于第三方库的环境类型声明很有用。如果缺少第三方库的某些声明,则可以再次使用相同的名称声明接口,并添加新的属性和方法。
示例:
我们可以扩展上面的Bird
接口以包括新的声明。
interface Bird {
color: string
eat(): void
}
就是这样!记住何时使用什么功能比迷失两者之间的细微差异要容易得多。
答案 6 :(得分:6)
其他答案都很棒! Type
可以做但Interface
做不到的其他事情
type Name = string | { FullName: string };
const myName = "Jon"; // works fine
const myFullName: Name = {
FullName: "Jon Doe", //also works fine
};
type Keys = "firstName" | "lastName";
type Name = {
[key in Keys]: string;
};
const myName: Name = {
firstName: "jon",
lastName: "doe",
};
extends
的接口中也支持)type Name = {
firstName: string;
lastName: string;
};
type Address = {
city: string;
};
const person: Name & Address = {
firstName: "jon",
lastName: "doe",
city: "scranton",
};
与type
相比,interface
的引入较晚,并且根据最新版的TS type
几乎可以完成interface
可以做的所有事情!
* Declaration merging
除外(个人观点:最好不提供类型上的支持,因为它可能导致代码不一致)
答案 7 :(得分:1)
接口和类型用于描述对象和基元的类型。接口和类型通常可以互换使用,并且通常提供相似的功能。通常,程序员会选择自己的偏好。
但是,接口只能描述对象和创建这些对象的类。因此,必须使用类型来描述诸如字符串和数字之类的基元。
以下是接口和类型之间的两个区别的示例:
// 1. Declaration merging (interface only)
// This is an extern dependency which we import an object of
interface externDependency { x: number, y: number; }
// When we import it, we might want to extend the interface, e.g. z:number
// We can use declaration merging to define the interface multiple times
// The declarations will be merged and become a single interface
interface externDependency { z: number; }
const dependency: externDependency = {x:1, y:2, z:3}
// 2. union types with primitives (type only)
type foo = {x:number}
type bar = { y: number }
type baz = string | boolean;
type foobarbaz = foo | bar | baz; // either foo, bar, or baz type
// instances of type foobarbaz can be objects (foo, bar) or primitives (baz)
const instance1: foobarbaz = {y:1}
const instance2: foobarbaz = {x:1}
const instance3: foobarbaz = true
答案 8 :(得分:1)
这是另一个区别。如果您能解释这种情况的原因,我会...给您买啤酒。
netstat
除非我有意实现某种OOP设计模式,或者需要如上所述进行合并(除非我拥有一个非常的充分理由)。
答案 9 :(得分:1)
在打字稿中,建议使用“接口”而不是“类型”。
“type”用于创建类型别名。
type Data=string
然后,您可以使用“数据”代替字符串
const name:string="Yilmaz"
const name:Data="Yilmaz"
别名非常有用,尤其是在处理泛型类型时。
你不能用“接口”来做到这一点。
您可以合并接口,但不能合并类型。
界面人{ 名称:字符串; }
interface Person {
age: number;
}
const me: Person = {
name: "Yilmaz Bingol",
age: 30
};
函数式编程用户使用“类型”,面向对象编程用户选择“接口”
您不能在接口上拥有计算或计算属性,而只能在类型中拥有。
type Fullname = "name" | “姓氏”
type Person= {
[key in Keys]: string
}
const me: Person = {
firstname: "Yilmaz",
lastname: "Bingol"
}
答案 10 :(得分:0)
除了已经提供的出色答案外,在扩展类型与接口之间还存在明显的差异。我最近遇到了几种界面无法完成工作的情况:
答案 11 :(得分:0)
文档已说明
- 一个区别是接口创建了一个新名称,该名称随处可见。类型别名不会创建新名称-例如,错误消息不会使用别名。在旧版本的TypeScript中,不能从中扩展或实现类型别名(也不能扩展/实现其他类型)。从2.7版开始,可以通过创建新的交集类型来扩展类型别名
- 另一方面,如果您无法使用界面表达某种形状,而需要使用并集或元组类型,则通常使用类型别名。
答案 12 :(得分:0)
好吧,“ typescriptlang”似乎建议尽可能在类型上使用接口。 Interface vs Type Alias
答案 13 :(得分:0)
索引编制也有所不同。
interface MyInterface {
foobar: string;
}
type MyType = {
foobar: string;
}
const exampleInterface: MyInterface = { foobar: 'hello world' };
const exampleType: MyType = { foobar: 'hello world' };
let record: Record<string, string> = {};
record = exampleType; // Compiles
record = exampleInterface; // Index signature is missing
因此,如果您要为对象建立索引,请考虑以下示例
看看这个question
答案 14 :(得分:0)
在编译速度方面,组合接口的性能优于类型交集:
[...]接口创建一个检测属性冲突的单一平面对象类型。这与交集类型相反,交集类型在检查有效类型之前先检查每个成分。与交叉点类型相反,接口之间的类型关系也被缓存。
来源:https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections
答案 15 :(得分:0)
Typescript手册给出了答案:关键区别在于,无法重新打开类型以添加新属性,而接口始终是可扩展的。
链接:https://www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases
答案 16 :(得分:0)
//为对象创建树结构。由于缺少交集(&),您无法对界面执行相同操作
type Tree<T> = T & { parent: Tree<T> };
//键入以限制变量只分配几个值。接口没有联合(|)
type Choise = "A" | "B" | "C";
//由于类型,您可以借助条件机制来声明NonNullable类型。
type NonNullable<T> = T extends null | undefined ? never : T;
//您可以将接口用于OOP并使用“实现”来定义对象/类的骨架
interface IUser {
user: string;
password: string;
login: (user: string, password: string) => boolean;
}
class User implements IUser {
user = "user1"
password = "password1"
login(user: string, password: string) {
return (user == user && password == password)
}
}
//您可以使用其他接口扩展接口
interface IMyObject {
label: string,
}
interface IMyObjectWithSize extends IMyObject{
size?: number
}
答案 17 :(得分:0)
documentation 中指出的主要区别在于,Interface
可以重新打开以添加新属性,而 Type alias
不能重新打开以添加新属性,例如:
没关系
interface x {
name: string
}
interface x {
age: number
}
这会抛出错误 Duplicate identifier y
type y = {
name: string
}
type y = {
age: number
}
除此之外,接口和类型别名是相似的。
答案 18 :(得分:0)
它们在语义上是不同的。
接口是 TS 类型系统中的常规语法元素。它是 TS 语法的原生部分。
而类型别名是一种语法糖。这是一种元编程。