516 typescript
这些语句(接口与类型)之间有什么区别?
interface X {
a: number
b: string
}
type X = {
a: number
b: string
};
Run Code Online (Sandbox Code Playgroud)
Bin*_*ree 462
与始终引入命名对象类型的接口声明不同,类型别名声明可以为任何类型的类型引入名称,包括基元,联合和交集类型.
规范接着提到:
接口类型与对象类型文字的类型别名有很多相似之处,但由于接口类型提供了更多功能,因此通常首选键入别名.例如,接口类型
Run Code Online (Sandbox Code Playgroud)interface Point { x: number; y: number; }
可以写为类型别名
Run Code Online (Sandbox Code Playgroud)type Point = { x: number; y: number; };
但是,这样做意味着丢失了以下功能:
- 接口可以在extends或implements子句中命名,但是对象类型文字的类型别名不能.
- 接口可以有多个合并声明,但对象类型文字的类型别名不能.
jab*_*tta 450
目前的答案和官方文件已经过时.对于那些刚接触TypeScript的人来说,如果没有示例,使用的术语就不清楚了.以下是最新差异列表.
两者都可用于描述对象的形状或函数签名.但语法不同.
接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
Run Code Online (Sandbox Code Playgroud)
输入别名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
Run Code Online (Sandbox Code Playgroud)
与接口不同,类型别名也可用于其他类型,例如基元,联合和元组.
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
Run Code Online (Sandbox Code Playgroud)
两者都可以扩展,但同样,语法也不同.另外,请注意,接口和类型别名不是互斥的.接口可以扩展类型别名,反之亦然.
接口扩展接口
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
Run Code Online (Sandbox Code Playgroud)
类型别名扩展类型别名
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Run Code Online (Sandbox Code Playgroud)
接口扩展类型别名
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Run Code Online (Sandbox Code Playgroud)
类型别名扩展接口
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
Run Code Online (Sandbox Code Playgroud)
类可以以相同的方式实现接口或类型别名.但请注意,类和接口被视为静态蓝图.因此,它们无法实现/扩展名为联合类型的类型别名.
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;
}
Run Code Online (Sandbox Code Playgroud)
与类型别名不同,接口可以多次定义,并将被视为单个接口(所有声明的成员都被合并).
// 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 };
Run Code Online (Sandbox Code Playgroud)
Mar*_*ark 93
对于typescrpt版本:4.3.4
我的个人惯例,我在下面描述,是这样的:
总是喜欢
interface
超过type
。
何时使用type
:
type
在为原始类型(字符串、布尔值、数字、bigint、符号等)定义别名时使用type
定义元组类型时使用type
定义函数类型时使用type
定义联合时使用type
在尝试通过组合重载对象类型中的函数时使用type
需要采取映射类型的优势,当何时使用interface
:
interface
用于type
不需要使用的所有对象类型(见上文)interface
时要采取申报合并的advatange。type
和之间最容易看到的区别interface
是只能type
用于为原语别名:
type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;
Run Code Online (Sandbox Code Playgroud)
这些示例都无法通过接口实现。
为原始值提供类型别名时,请使用type
关键字。
元组只能通过type
关键字输入:
type row = [colOne: number, colTwo: string];
Run Code Online (Sandbox Code Playgroud)
在type
为元组提供类型时使用关键字。
函数可以通过type
和interface
关键字输入:
// via type
type Sum = (x: number, y: number) => number;
// via interface
interface Sum {
(x: number, y: number): number;
}
Run Code Online (Sandbox Code Playgroud)
由于可以通过任何一种方式实现相同的效果,因此规则将type
在这些场景中使用,因为它更容易阅读(并且不那么冗长)。
type
在定义函数类型时使用。
联合类型只能通过type
关键字实现:
type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';
// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;
Run Code Online (Sandbox Code Playgroud)
定义联合类型时,使用type
关键字
javascript 中的对象是键/值映射,而“对象类型”是打字稿键入这些键/值映射的方式。二者interface
并type
可以为对象提供类型时作为原始问题明确使用。那么什么时候对对象类型使用type
vsinterface
呢?
使用类型和组合,我可以做这样的事情:
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')
Run Code Online (Sandbox Code Playgroud)
打字稿非常高兴。如果我尝试使用接口扩展它呢:
interface StrAndNumLogger extends NumLogger {
log: (val: string) => void;
};
Run Code Online (Sandbox Code Playgroud)
的声明StrAndNumLogger
给了我一个错误:
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
);
Run Code Online (Sandbox Code Playgroud)
然后我可以像这样使用它:
test.timedTest('this is my custom test', () => {
expect(true).toBe(true);
});
Run Code Online (Sandbox Code Playgroud)
现在,一旦测试完成,该测试所用的时间将打印到控制台。伟大的!只有一个问题 - typescript 不知道我添加了一个timedTest
函数,所以它会在编辑器中抛出一个错误(代码运行良好,但 TS 会生气)。
为了解决这个问题,我需要告诉 TS 在 jest 已经可用的现有类型之上有一个新类型。为此,我可以这样做:
declare namespace jest {
interface It {
timedTest: (name: string, fn: (mockTrack: Mock) => any, timeout?: number) => void;
}
}
Run Code Online (Sandbox Code Playgroud)
由于接口是如何工作的,这种类型的声明将被合并与导出的类型声明@types/jest
。所以我不只是重新声明jest.It
; 我扩展jest.It
了一个新函数,以便 TS 现在知道我的自定义测试函数。
这种类型的事情是不可能的type
关键字。如果@types/jest
使用type
关键字声明它们的类型,我将无法使用我自己的自定义类型扩展这些类型,因此没有什么好方法让 TS 对我的新函数感到满意。这个interface
关键字独有的过程称为声明合并。
声明合并也可以像这样在本地进行:
interface Person {
name: string;
}
interface Person {
age: number;
}
// no error
const person: Person = {
name: 'Mark',
age: 25
};
Run Code Online (Sandbox Code Playgroud)
如果我对type
关键字做了完全相同的事情,我会得到一个错误,因为类型不能被重新声明/合并。在现实世界中,javascript 对象很像这个interface
例子;它们可以在运行时使用新字段动态更新。
因为接口声明可以合并,接口比类型更准确地表示 javascript 对象的动态特性,因此它们应该是首选。
使用type
关键字,我可以利用这样的映射类型:
type Fruit = 'apple' | 'orange' | 'banana';
type FruitCount = {
[key in Fruit]: number;
}
const fruits: FruitCount = {
apple: 2,
orange: 3,
banana: 4
};
Run Code Online (Sandbox Code Playgroud)
这不能通过接口完成:
type Fruit = 'apple' | 'orange' | 'banana';
// ERROR:
interface FruitCount {
[key in Fruit]: number;
}
Run Code Online (Sandbox Code Playgroud)
当需要利用映射类型时,使用type
关键字
大多数时候,对象类型的简单类型别名的作用与接口非常相似。
interface Foo { prop: string }
type Bar = { prop: string };
Run Code Online (Sandbox Code Playgroud)
但是,一旦您需要组合两个或多个类型,您就可以选择使用接口扩展这些类型,或者将它们交叉在一个类型别名中,这就是差异开始重要的时候。
接口创建一个单一的平面对象类型来检测属性冲突,这通常很重要!另一方面,交叉点只是递归地合并属性,在某些情况下永远不会产生。界面也始终显示得更好,而交叉点的类型别名不能在其他交叉点的一部分中显示。接口之间的类型关系也被缓存,而不是作为一个整体的交集类型。最后一个值得注意的区别是,在检查目标交叉点类型时,在检查“有效”/“扁平”类型之前检查每个成分。
出于这个原因,建议使用接口/扩展扩展类型而不是创建交叉类型。
更多关于打字稿维基。
Yog*_*ity 78
type
?通用转换
使用type
时,您要变换多个类型为单个通用型。
例子:
type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T
Run Code Online (Sandbox Code Playgroud)
类型别名
我们可以使用type
为难以阅读且不方便反复键入的长类型或复杂类型创建别名。
例子:
type Primitive = number | string | boolean | null | undefined
Run Code Online (Sandbox Code Playgroud)
创建这样的别名可以使代码更加简洁和可读。
类型捕获
type
当类型未知时,使用来捕获对象的类型。
例子:
const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit
Run Code Online (Sandbox Code Playgroud)
在这里,我们获取 的未知类型orange
,将其命名为 a Fruit
,然后使用Fruit
来创建新的类型安全对象apple
。
interface
?多态性
Aninterface
是实现数据形状的合同。使用接口明确表示它旨在实现并用作关于如何使用对象的合同。
例子:
interface Bird {
size: number
fly(): void
sleep(): void
}
class Hummingbird implements Bird { ... }
class Bellbird implements Bird { ... }
Run Code Online (Sandbox Code Playgroud)
尽管您可以使用type
来实现这一点,但 Typescript 更像是一种面向对象的语言,并且在面向对象的语言interface
中占有特殊的地位。interface
当您在团队环境中工作或为开源社区做出贡献时,更容易阅读代码。对于来自其他面向对象语言的新程序员来说,这也很容易。
官方 Typescript文档还说:
......我们建议使用
interface
在type
可能的情况下别名。
这也表明type
更倾向于创建类型别名而不是创建类型本身。
声明合并
您可以使用 的声明合并功能interface
将新属性和方法添加到已声明的interface
. 这对于第三方库的环境类型声明很有用。当第三方库缺少某些声明时,您可以使用相同的名称再次声明接口并添加新的属性和方法。
例子:
我们可以扩展上述Bird
接口以包含新的声明。
interface Bird {
color: string
eat(): void
}
Run Code Online (Sandbox Code Playgroud)
就是这样!记住何时使用什么比在两者之间的细微差别中迷失更容易。
Kar*_*ski 68
从TypeScript 3.2开始,以下情况属实:
DRe*_*act 50
2024 年更新 -
类型别名和接口非常相似,在许多情况下您可以在它们之间自由选择。接口的几乎所有功能都可以在类型中使用,主要区别在于类型不能重新打开以添加新属性,而接口始终是可扩展的。
nic*_*ckf 25
https://www.typescriptlang.org/docs/handbook/advanced-types.html
一个区别是接口创建一个在任何地方使用的新名称.类型别名不会创建新名称 - 例如,错误消息将不使用别名.
Prz*_*ski 18
// 为一个对象创建一个树结构。由于缺少交集 (&),您不能对界面做同样的事情
type Tree<T> = T & { parent: Tree<T> };
Run Code Online (Sandbox Code Playgroud)
// type 限制一个变量只能分配几个值。接口没有联合 (|)
type Choise = "A" | "B" | "C";
Run Code Online (Sandbox Code Playgroud)
// 由于类型,您可以通过条件机制声明 NonNullable 类型。
type NonNullable<T> = T extends null | undefined ? never : T;
Run Code Online (Sandbox Code Playgroud)
// 您可以使用 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)
}
}
Run Code Online (Sandbox Code Playgroud)
// 你可以用其他接口扩展接口
interface IMyObject {
label: string,
}
interface IMyObjectWithSize extends IMyObject{
size?: number
}
Run Code Online (Sandbox Code Playgroud)
cap*_*ian 14
索引的差异。
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
Run Code Online (Sandbox Code Playgroud)
因此,如果您想为对象建立索引,请考虑这个示例
评价差异
ExtendFirst
查看when FirstLevelType
is接口的结果类型
/**
* When FirstLevelType is interface
*/
interface FirstLevelType<A, Z> {
_: "typeCheck";
};
type TestWrapperType<T, U> = FirstLevelType<T, U>;
const a: TestWrapperType<{ cat: string }, { dog: number }> = {
_: "typeCheck",
};
// { cat: string; }
type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _>
? T
: "not extended";
Run Code Online (Sandbox Code Playgroud)
ExtendFirst
查看when FirstLevelType
is a type的结果类型:
/**
* When FirstLevelType is type
*/
type FirstLevelType<A, Z>= {
_: "typeCheck";
};
type TestWrapperType<T, U> = FirstLevelType<T, U>;
const a: TestWrapperType<{ cat: string }, { dog: number }> = {
_: "typeCheck",
};
// unknown
type ExtendFirst = typeof a extends FirstLevelType<infer T, infer _>
? T
: "not extended";
Run Code Online (Sandbox Code Playgroud)
Ani*_*kwu 11
文档中指出的主要区别是Interface
可以重新打开以添加新属性,但Type alias
不能重新打开以添加新属性,例如:
还行吧
interface x {
name: string
}
interface x {
age: number
}
Run Code Online (Sandbox Code Playgroud)
这会抛出错误Duplicate identifier y
type y = {
name: string
}
type y = {
age: number
}
Run Code Online (Sandbox Code Playgroud)
除此之外,接口和类型别名都很相似。
在打字稿中,建议使用“接口”而不是“类型”。
\n“类型”用于创建type aliases
。你不能用“接口”来做到这一点。
type Data=string\n
Run Code Online (Sandbox Code Playgroud)\n然后您可以使用“Data”而不是使用字符串
\nconst name:string="Yilmaz"\nconst name:Data="Yilmaz"\n
Run Code Online (Sandbox Code Playgroud)\n别名非常有用,尤其是在处理泛型类型时。
\nDeclaration Merging:
您可以合并接口,但不能合并类型。
interface Person {\n name: string;\n }\n\n interface Person {\n age: number;\n }\n // we have to provide properties in both Person\n const me: Person = {\n name: "Yilmaz",\n age: 30\n };\n
Run Code Online (Sandbox Code Playgroud)\n函数式编程用户使用“类型”,面向对象编程用户选择“接口”
\n您可以\xe2\x80\x99t 在接口上但在类型中计算或计算属性。
\n type Fullname = "firstname" | "lastname"\n\n type Person= {\n [key in Fullname]: string\n }\n\n const me: Person = {\n firstname: "Yilmaz",\n lastname: "Bingol"\n }\n
Run Code Online (Sandbox Code Playgroud)\n在我的日常开发中,当我不知道该选择哪个时,我会使用这个备忘单。
欲了解更多信息,您可以阅读我的博客:https://medium.com/@magenta2127/use-which-interface-or-type-alias-in-typescript-bdfaf2e882ae
其他答案都很棒!很少有其他Type
可以做但Interface
不能做的事情
type Name = string | { FullName: string };
const myName = "Jon"; // works fine
const myFullName: Name = {
FullName: "Jon Doe", //also works fine
};
Run Code Online (Sandbox Code Playgroud)
type Keys = "firstName" | "lastName";
type Name = {
[key in Keys]: string;
};
const myName: Name = {
firstName: "jon",
lastName: "doe",
};
Run Code Online (Sandbox Code Playgroud)
extends
)type Name = {
firstName: string;
lastName: string;
};
type Address = {
city: string;
};
const person: Name & Address = {
firstName: "jon",
lastName: "doe",
city: "scranton",
};
Run Code Online (Sandbox Code Playgroud)
也不type
是后来引入的interface
,根据最新版本的 TStype
可以做*几乎所有可以做的事情,interface
还有更多!
*除了Declaration merging
(个人意见:不支持类型是好的,因为它可能导致代码不一致)
接口和类型用于描述对象和基元的类型。接口和类型通常可以互换使用,并且通常提供类似的功能。通常是程序员根据自己的喜好进行选择。
然而,接口只能描述对象和创建这些对象的类。因此,必须使用类型来描述字符串和数字等基元。
以下是接口和类型之间的 2 个差异的示例:
// 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
Run Code Online (Sandbox Code Playgroud)
小智 5
Typescript 手册给出了答案:关键区别在于,不能重新打开类型以添加新属性与始终可扩展的接口。
链接:https : //www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases
归档时间: |
|
查看次数: |
89349 次 |
最近记录: |