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
};
当前回答
“typescriptlang”似乎建议尽可能使用接口而不是类型。接口与类型别名
其他回答
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 };
索引的差异。
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";
界面专门设计用于描述对象形状;然而,类型在某种程度上类似于可以用于为任何类型创建新名称的接口。
我们可以说,一个接口可以通过多次声明来扩展;而类型是封闭的。
https://itnext.io/interfaces-vs-types-in-typescript-cf5758211910
https://www.typescriptlang.org/docs/handbook/advanced-types.html
一个区别是,接口创建了一个新名称,该名称在任何地方都可以使用。键入别名不会创建新名称-例如,错误消息不会使用别名。
演示递归地重写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.
})