TypeScript中的这些语句(接口与类型)有什么区别?
interface X {
a: number
b: string
}
type X = {
a: number
b: string
};
TypeScript中的这些语句(接口与类型)有什么区别?
interface X {
a: number
b: string
}
type X = {
a: number
b: string
};
当前回答
何时使用类型?
通用转换
将多个类型转换为单个泛型类型时,请使用该类型。
例子:
type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T
类型别名
我们可以使用该类型为长类型或复杂类型创建别名,这些类型既难以阅读,又不方便反复键入。
例子:
type Primitive = number | string | boolean | null | undefined
创建这样的别名使代码更加简洁易读。
类型捕获
当类型未知时,使用该类型捕获对象的类型。
例子:
const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit
在这里,我们得到了未知类型的橙子,称其为水果,然后使用水果创建一个新型的安全对象苹果。
何时使用界面?
多态性
接口是实现数据形状的契约。使用该接口明确表示它将被实现并用作关于如何使用对象的约定。
例子:
interface Bird {
size: number
fly(): void
sleep(): void
}
class Hummingbird implements Bird { ... }
class Bellbird implements Bird { ... }
尽管您可以使用类型来实现这一点,但Typescript更多地被视为一种面向对象的语言,并且接口在面向对象语言中具有特殊的地位。当您在团队环境中工作或为开源社区做出贡献时,使用界面更容易阅读代码。对于来自其他面向对象语言的新程序员来说也很容易。
官方的Typescript文档还说:
…我们建议尽可能在类型别名上使用接口。
这也表明该类型更适合于创建类型别名,而不是创建类型本身。
声明合并
您可以使用接口的声明合并功能向已声明的接口添加新的财产和方法。这对于第三方库的环境类型声明很有用。当第三方库缺少某些声明时,可以使用相同的名称再次声明接口,并添加新的财产和方法。
例子:
我们可以扩展上述Bird接口以包含新的声明。
interface Bird {
color: string
eat(): void
}
就是这样!记住什么时候使用比迷失在两者之间的细微差别中更容易。
其他回答
文档中指出的关键区别在于,可以重新打开接口以添加新属性,但不能重新打开类型别名以添加新的属性,例如:
这没问题
interface x {
name: string
}
interface x {
age: number
}
这将抛出错误Duplicate identifier y
type y = {
name: string
}
type y = {
age: number
}
除此之外,接口和类型别名类似。
2019年更新
目前的答案和官方文件已经过时。对于那些新接触TypeScript的人来说,如果没有例子,使用的术语就不清楚了。以下是最新差异列表。
1.对象/功能
两者都可以用来描述对象或函数签名的形状。但语法不同。
界面
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;
2.其他类型
与接口不同,类型别名也可以用于其他类型,如基元、联合和元组。
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
3.延伸
两者都可以扩展,但语法也不同。此外,请注意,接口和类型别名不是互斥的。接口可以扩展类型别名,反之亦然。
接口扩展接口
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; };
4.工具
类可以以相同的方式实现接口或类型别名。但是请注意,类和接口被视为静态蓝图。因此,它们不能实现/扩展命名联合类型的类型别名。
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;
}
5.申报合并
与类型别名不同,接口可以定义多次,并将被视为单个接口(合并所有声明的成员)。
// 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 };
演示递归地重写Object-Literal类型和接口而不是类成员/财产/函数的能力。
此外,当Record<any,string|number>由于接口等原因而无法工作时,如何区分和键入检查差异以及解决上述问题的方法也可以解决。这将允许对猫鼬类型进行以下简化:https://github.com/wesleyolis/mongooseRelationalTypesmongooseRelationalTypes、DeepPopulate、populate
此外,还有一系列其他方法来实现高级类型泛型和类型推理,以及围绕它的速度怪癖,这些都是一些小技巧,可以通过多次试验和错误来获得正确的结果。
打字游戏场:单击此处查看现场游戏场地中的所有示例
class TestC {
constructor(public a: number, public b: string, private c: string) {
}
}
class TestD implements Record<any, any> {
constructor(public a: number, public b: string, private c: string) {
}
test() : number {
return 1;
}
}
type InterfaceA = {
a: string,
b: number,
c: Date
e: TestC,
f: TestD,
p: [number],
neastedA: {
d: string,
e: number
h: Date,
j: TestC
neastedB: {
d: string,
e: number
h: Date,
j: TestC
}
}
}
type TCheckClassResult = InterfaceA extends Record<any, unknown> ? 'Y': 'N' // Y
const d = new Date();
type TCheckClassResultClass = typeof d extends Record<any, unknown> ? 'Y': 'N' // N
const metaData = Symbol('metaData');
type MetaDataSymbol = typeof metaData;
// Allows us to not recuse into class type interfaces or traditional interfaces, in which properties and functions become optional.
type MakeErrorStructure<T extends Record<any, any>> = {
[K in keyof T] ?: (T[K] extends Record<any, unknown> ? MakeErrorStructure<T[K]>: T[K] & Record<MetaDataSymbol, 'customField'>)
}
type MakeOptional<T extends Record<any, any>> = {
[K in keyof T] ?: T[K] extends Record<any, unknown> ? MakeOptional<T[K]> : T[K]
}
type RRR = MakeOptional<InterfaceA>
const res = {} as RRR;
const num = res.e!.a; // type == number
const num2 = res.f!.test(); // type == number
使特定形状的递归形状或键递归
type MakeRecusive<Keys extends string, T> = {
[K in Keys]: T & MakeRecusive<K, T>
} & T
type MakeRecusiveObectKeys<TKeys extends string, T> = {
[K in keyof T]: K extends TKeys ? T[K] & MakeRecusive<K, T[K]>: T[K]
}
如何为记录类型应用类型约束,该约束可以验证诸如鉴别器之类的接口:
type IRecordITypes = string | symbol | number;
// Used for checking interface, because Record<'key', Value> excludeds interfaces
type IRecord<TKey extends IRecordITypes, TValue> = {
[K in TKey as `${K & string}`] : TValue
}
// relaxies the valiation, older versions can't validate.
// type IRecord<TKey extends IRecordITypes, TValue> = {
// [index: TKey] : TValue
// }
type IRecordAnyValue<T extends Record<any,any>, TValue> = {
[K in keyof T] : TValue
}
interface AA {
A : number,
B : string
}
interface BB {
A: number,
D: Date
}
// This approach can also be used, for indefinitely recursive validation like a deep populate, which can't determine what validate beforehand.
interface CheckRecConstraints<T extends IRecordAnyValue<T, number | string>> {
}
type ResA = CheckRecConstraints<AA> // valid
type ResB = CheckRecConstraints<BB> // invalid
Alternative for checking keys:
type IRecordKeyValue<T extends Record<any,any>, TKey extends IRecordITypes, TValue> =
{
[K in keyof T] : (TKey & K) extends never ? never : TValue
}
// This approach can also be used, for indefinitely recursive validation like a deep populate, which can't determine what validate beforehand.
interface CheckRecConstraints<T extends IRecordKeyValue<T, number | string, number | string>> {
A : T
}
type UUU = IRecordKeyValue<AA, string, string | number>
type ResA = CheckRecConstraints<AA> // valid
type ResB = CheckRecConstraints<BB> // invalid
然而,使用鉴别器的示例,对于速度,我宁愿使用字面上定义每个要记录的键,然后传递来生成混合值,因为使用更少的内存,而且比这种方法更快。
type EventShapes<TKind extends string> = IRecord<TKind, IRecordITypes> | (IRecord<TKind, IRecordITypes> & EventShapeArgs)
type NonClassInstance = Record<any, unknown>
type CheckIfClassInstance<TCheck, TY, TN> = TCheck extends NonClassInstance ? 'N' : 'Y'
type EventEmitterConfig<TKind extends string = string, TEvents extends EventShapes<TKind> = EventShapes<TKind>, TNever = never> = {
kind: TKind
events: TEvents
noEvent: TNever
}
type UnionDiscriminatorType<TKind extends string, T extends Record<TKind, any>> = T[TKind]
type PickDiscriminatorType<TConfig extends EventEmitterConfig<any, any, any>,
TKindValue extends string,
TKind extends string = TConfig['kind'],
T extends Record<TKind, IRecordITypes> & ({} | EventShapeArgs) = TConfig['events'],
TNever = TConfig['noEvent']> =
T[TKind] extends TKindValue
? TNever
: T extends IRecord<TKind, TKindValue>
? T extends EventShapeArgs
? T['TArgs']
: [T]
: TNever
type EventEmitterDConfig = EventEmitterConfig<'kind', {kind: string | symbol}, any>
type EventEmitterDConfigKeys = EventEmitterConfig<any, any> // Overide the cached process of the keys.
interface EventEmitter<TConfig extends EventEmitterConfig<any, any, any> = EventEmitterDConfig,
TCacheEventKinds extends string = UnionDiscriminatorType<TConfig['kind'], TConfig['events']>
> {
on<TKey extends TCacheEventKinds,
T extends Array<any> = PickDiscriminatorType<TConfig, TKey>>(
event: TKey,
listener: (...args: T) => void): this;
emit<TKey extends TCacheEventKinds>(event: TKey, args: PickDiscriminatorType<TConfig, TKey>): boolean;
}
用法示例:
interface EventA {
KindT:'KindTA'
EventA: 'EventA'
}
interface EventB {
KindT:'KindTB'
EventB: 'EventB'
}
interface EventC {
KindT:'KindTC'
EventC: 'EventC'
}
interface EventArgs {
KindT:1
TArgs: [string, number]
}
const test :EventEmitter<EventEmitterConfig<'KindT', EventA | EventB | EventC | EventArgs>>;
test.on("KindTC",(a, pre) => {
})
更好的方法来区分类型并从映射中选择类型以缩小范围,这通常会导致更快的性能和更少的类型操作开销,并允许改进缓存。与前面的示例相比。
type IRecordKeyValue<T extends Record<any,any>, TKey extends IRecordITypes, TValue> =
{
[K in keyof T] : (TKey & K) extends never ? never : TValue
}
type IRecordKeyRecord<T extends Record<any,any>, TKey extends IRecordITypes> =
{
[K in keyof T] : (TKey & K) extends never ? never : T[K] // need to figure out the constrint here for both interface and records.
}
type EventEmitterConfig<TKey extends string | symbol | number, TValue, TMap extends IRecordKeyValue<TMap, TKey, TValue>> = {
map: TMap
}
type PickKey<T extends Record<any,any>, TKey extends any> = (T[TKey] extends Array<any> ? T[TKey] : [T[TKey]]) & Array<never>
type EventEmitterDConfig = EventEmitterConfig<string | symbol, any, any>
interface TDEventEmitter<TConfig extends EventEmitterConfig<any, any, TConfig['map']> = EventEmitterDConfig,
TMap = TConfig['map'],
TCacheEventKinds = keyof TMap
> {
on<TKey extends TCacheEventKinds, T extends Array<any> = PickKey<TMap, TKey>>(event: TKey,
listener: (...args: T) => void): this;
emit<TKey extends TCacheEventKinds, T extends Array<any> = PickKey<TMap, TKey>>(event: TKey, ...args: T): boolean;
}
type RecordToDiscriminateKindCache<TKindType extends string | symbol | number, TKindName extends TKindType, T extends IRecordKeyRecord<T, TKindType>> = {
[K in keyof T] : (T[K] & Record<TKindName, K>)
}
type DiscriminateKindFromCache<T extends IRecordKeyRecord<T, any>> = T[keyof T]
用法示例:
interface EventA {
KindT:'KindTA'
EventA: 'EventA'
}
interface EventB {
KindT:'KindTB'
EventB: 'EventB'
}
interface EventC {
KindT:'KindTC'
EventC: 'EventC'
}
type EventArgs = [number, string]
type Items = {
KindTA : EventA,
KindTB : EventB,
KindTC : EventC
//0 : EventArgs,
}
type DiscriminatorKindTypeUnionCache = RecordToDiscriminateKindCache<string
//| number,
"KindGen", Items>;
type CachedItemForSpeed = DiscriminatorKindTypeUnionCache['KindTB']
type DiscriminatorKindTypeUnion = DiscriminateKindFromCache<DiscriminatorKindTypeUnionCache>;
function example() {
const test: DiscriminatorKindTypeUnion;
switch(test.KindGen) {
case 'KindTA':
test.EventA
break;
case 'KindTB':
test.EventB
break;
case 'KindTC':
test.EventC
case 0:
test.toLocaleString
}
}
type EmitterConfig = EventEmitterConfig<string
//| number
, any, Items>;
const EmitterInstance :TDEventEmitter<EmitterConfig>;
EmitterInstance.on("KindTB",(a, b) => {
a.
})
其他答案很棒!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",
};
类型中的交集(但是,在带有扩展的接口中也支持)
type Name = {
firstName: string;
lastName: string;
};
type Address = {
city: string;
};
const person: Name & Address = {
firstName: "jon",
lastName: "doe",
city: "scranton",
};
与接口相比,这种类型也不是后来才引入的,根据最新发布的TS类型,它几乎可以做任何接口可以做的事情,而且更多!
*除了声明合并(个人观点:很好,它在类型中不受支持,因为它可能导致代码不一致)
索引的差异。
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
相关问题:类型中缺少索引签名(仅在接口上,而不是在类型别名上)
因此,如果您想为对象编制索引,请考虑这个示例
看看这个问题和这个关于违反利斯科夫原则的问题
评估中的差异
当FirstLevelType为接口时,请查看ExtendeFirst的结果类型
/**
* 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";
当FirstLevelType为类型时,请查看ExtendeFirst的结果类型:
/**
* 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";