TypeScript中的接口与类型

TypeScript中这些语句(interface vstype)有什么区别?

interface X {a: numberb: string}
type X = {a: numberb: string};
646804 次浏览

2021年3月更新:较新的TypeScript手册(也在nju-clc中提到)下面的答案)有一个部分接口与类型别名解释了差异。


原始答案(2016)

根据(现已存档)TypeScript语言规范

与总是引入命名对象类型的接口声明不同,类型别名声明可以为任何类型引入名称,包括原始、联合和交集类型。

该规范接着提到:

接口类型与对象类型的类型别名有许多相似之处文字,但由于接口类型提供了更多的功能通常优先于键入别名。例如,接口类型

interface Point {x: number;y: number;}

可以写成类型别名

type Point = {x: number;y: number;};

但是,这样做意味着失去以下功能:

  • 接口可以在扩展或实现子句中命名,但对象类型文字的类型别名不能自TS 2.7起不再为真。
  • 一个接口可以有多个合并声明,但对象类型文字的类型别名不能。

https://www.typescriptlang.org/docs/handbook/advanced-types.html

一个区别是接口创建一个在任何地方都使用的新名称。类型别名不会创建新名称-例如,错误消息不会使用别名。

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.其他类型

与接口不同,类型别名还可用于其他类型,例如原语、联合和元组。

// primitivetype Name = string;
// objecttype PartialPointX = { x: number; };type PartialPointY = { y: number; };
// uniontype PartialPoint = PartialPointX | PartialPointY;
// tupletype 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 typeclass 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 };

从TypeScript 3.2(2018年11月)开始,以下是真的:

方面类型界面
可以描述功能
可以描述构造函数
可以描述元组
接口可以扩展它⚠️
类可以扩展它🚫
类可以实现它(implements⚠️
可以和另一个同类相交⚠️
可以和另一个同类建立联盟🚫
可用于创建映射类型🚫
可以使用映射类型进行映射
在错误消息和日志中展开🚫
可以增强🚫
可以是递归的⚠️

⚠️在某些情况下

类型示例:

//为对象创建树形结构。由于缺乏交集(&),你不能对接口执行相同的操作

type Tree<T> = T & { parent: Tree<T> };

//type限制变量只能分配几个值。接口没有联合(|)

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}

其他答案都很棒!很少有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相比,根据TStype的最新版本,几乎可以做interface可以做的所有事情,甚至更多!


*除了Declaration merging个人意见:类型中不支持它是件好事,因为它可能会导致代码不一致

接口vs类型

接口和类型用于描述对象和原语的类型。接口和类型通常可以互换使用,并且通常提供类似的功能。通常由程序员选择自己的偏好。

但是,接口只能描述创建这些对象的对象和类。因此,必须使用类型来描述字符串和数字等原语。

以下是接口和类型之间的两个区别的示例:

// 1. Declaration merging (interface only)
// This is an extern dependency which we import an object ofinterface 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 interfaceinterface 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

什么时候使用type


泛型转换

当您将多个类型转换为单个泛型类型时,请使用type

示例:

type Nullable<T> = T | null | undefinedtype 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 orangelet apple: Fruit

在这里,我们得到未知类型的orange,将其称为Fruit,然后使用Fruit创建一个新的类型安全对象apple


什么时候使用interface


多态

interface是实现数据形状的契约。使用接口明确表示它旨在实现并用作有关如何使用对象的契约。

示例:

interface Bird {size: numberfly(): voidsleep(): void}
class Hummingbird implements Bird { ... }class Bellbird implements Bird { ... }

尽管你可以使用type来实现这一点,但TypeScript更多地被视为一种面向对象语言,而interface在面向对象语言中占有特殊地位。当你在团队环境中工作或为开源社区做出贡献时,使用interface阅读代码更容易。来自其他面向对象语言的新程序员也很容易。

官方TypeScript留档也说:

…我们建议尽可能使用interface而不是type别名。

这也表明type更适合创建类型别名,而不是创建类型本身。


合并声明

您可以使用interface的声明合并功能向已经声明的interface添加新的属性和方法。这对于第三方库的环境类型声明很有用。当第三方库缺少某些声明时,您可以使用相同的名称再次声明接口并添加新的属性和方法。

示例:

我们可以扩展上面的Bird接口以包含新的声明。

interface Bird {color: stringeat(): void}

就是这样!记住何时使用什么比迷失在两者之间的细微差别中更容易。

嗯,'typeredtlang'似乎建议在可能的情况下使用接口而不是类型。@typeredtlang接口与类型别名

还有另一个区别。如果你能解释这种情况的原因或原因,我会请你喝啤酒:

enum Foo { a = 'a', b = 'b' }
type TFoo = {[k in Foo]: boolean;}
const foo1: TFoo = { a: true, b: false} // good// const foo2: TFoo = { a: true }       // bad: missing b// const foo3: TFoo = { a: true, b: 0}  // bad: b is not a boolean
// So type does roughly what I'd expect and want
interface IFoo {//  [k in Foo]: boolean;/*Uncommenting the above line gives the following errors:A computed property name in an interface must refer to an expression whose type is aliteral type or a 'unique symbol' type.A computed property name must be of type 'string', 'number', 'symbol', or 'any'.Cannot find name 'k'.*/}
// ???

这让我想说去他的接口,除非我有意实现一些OOP设计模式,或者需要如上所述的合并(除非我有一个非常的充分理由,否则我永远不会这样做)。

索引中的差异。

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;      // Compilesrecord = exampleInterface; // Index signature is missing

相关问题:类型中缺少索引签名(仅在接口上,不在类型别名上)

所以请考虑这个例子,如果你想索引你的对象

看看这个关于违反Liskov原则的问题这一个

评价差异

FirstLevelType是接口时,请参阅ExtendFirst的结果类型

/*** 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是类型时,请参阅ExtendFirst的结果类型:

/*** When FirstLevelType is type*/type FirstLevelType<A, Z>= {_: "typeCheck";};
type TestWrapperType<T, U> = FirstLevelType<T, U>;

const a: TestWrapperType<{ cat: string }, { dog: number }> = {_: "typeCheck",};
// unknowntype ExtendFirst = typeof a extends FirstLevelType<infer T, infer _>? T: "not extended";

在编译速度方面,组合接口的性能优于类型交集:

[…]接口创建一个检测属性冲突的平面对象类型。这与交集类型相反,交集类型在检查有效类型之前检查每个组成部分。接口之间的类型关系也被缓存,而不是交集类型。

来源:https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections

TypeScript手册给出了答案:

几乎接口的所有功能都可以在类型中使用。
关键的区别在于不能重新打开类型以添加新的属性与总是可扩展的接口。

2021年相关

对于打字稿版本:4.3.4


TLDR;

我个人的惯例,我在下面描述,是这样的:

总是喜欢interface而不是type

什么时候使用type

  • 为基本类型(字符串、布尔值、数字、bigint、符号等)定义别名时使用type
  • 定义元组类型时使用type
  • 定义函数类型时使用type
  • 定义联合时使用type
  • 尝试通过组合重载对象类型中的函数时使用type
  • 当需要利用映射类型时使用type

什么时候使用interface

  • 对所有不需要使用type的对象类型使用interface(见上文)
  • 当您想利用声明合并时,请使用interface

原始类型

typeinterface之间最简单的区别是只有type可以用来别名基元:

type Nullish = null | undefined;type Fruit = 'apple' | 'pear' | 'orange';type Num = number | bigint;

这些例子都不可能通过接口实现。

💡为基元值提供类型别名时,请使用type关键字。

元组类型

元组只能通过type关键字键入:

type row = [colOne: number, colTwo: string];

💡在为元组提供类型时使用type关键字。

函数类型

函数可以通过typeinterface关键字键入:

// via typetype Sum = (x: number, y: number) => number;
// via interfaceinterface 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中的对象是键/值映射,“对象类型”是类型脚本键入这些键/值映射的方式。当为对象提供类型时,interfacetype都可以使用,正如最初的问题所表明的那样。那么你什么时候使用type vsinterface作为对象类型?

交集vs继承

使用类型和组合,我可以做这样的事情:

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的声明给了我一个错误

Interface 'StrAndNumLogger' incorrectly extends interface 'NumLogger'

对于接口,子类型必须与超类型中声明的类型完全匹配,否则TS将抛出类似上述的错误。

💡当尝试重载对象类型中的函数时,最好使用type关键字。

合并声明

TypeScript中接口与类型区别的关键方面是,它们可以在声明后使用新功能进行扩展。当您想要扩展从节点模块导出的类型时,会出现此功能的常见用例。例如,@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);});

现在,一旦测试完成,该测试所用的时间将打印到控制台。太好了!只有一个问题-打字脚本不知道我添加了一个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/jesttype关键字声明了它们的类型,我就无法用我自己的自定义类型扩展这些类型,因此就没有好的方法让TS对我的新功能感到满意。这个interface关键字独有的过程称为申报合并

声明合并也可以在本地这样做:

interface Person {name: string;}
interface Person {age: number;}
// no errorconst person: Person = {name: 'Mark',age: 25};

如果我用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};

这不能用接口来完成:

type Fruit = 'apple' | 'orange' | 'banana';
// ERROR:interface FruitCount {[key in Fruit]: number;}

💡当需要利用映射类型时,使用type关键字

性能

大多数时候,对象类型的简单类型别名的行为与接口非常相似。

interface Foo { prop: string }
type Bar = { prop: string };

但是,一旦您需要组合两个或多个类型,您就可以选择使用接口扩展这些类型,或者在类型别名中交叉它们,这就是差异开始重要的时候。

接口创建了一个单一的平面对象类型来检测属性冲突,这通常很重要!另一方面,交集只是递归地合并属性,在某些情况下永远不会产生。接口也显示得更好,而交集的类型别名不能在其他交集的一部分中显示。接口之间的类型关系也被缓存,而不是整个交集类型。最后一个值得注意的区别是,当检查目标交集类型时,在检查“有效”/“扁平化”类型之前,会检查每个组成部分。

出于这个原因,建议使用接口/扩展扩展类型而不是创建交集类型。

更多在打字稿wiki

留档中指出的关键区别是Interface可以重新打开以添加新属性,但Type alias不能重新打开以添加新属性,例如:

这样就可以了

interface x {name: string}
interface x {age: number}

这将引发错误Duplicate identifier y

type y = {name: string}
type y = {age: number}

除此之外,接口和类型别名是相似的。

在打字稿中,“接口”被推荐而不是“类型”。

  • “type”用于创建type aliases。您不能使用“接口”执行此操作。

     type Data=string

    然后,您可以使用“数据”而不是使用字符串

    const name:string="Yilmaz"const name:Data="Yilmaz"

别名非常有用,特别是在泛型类型中。

  • Declaration Merging:可以合并接口,但不能合并类型。

    interface Person {name: string;}
    interface Person {age: number;}// we have to provide properties in both Personconst me: Person = {name: "Yilmaz",age: 30};
  • 函数式编程用户选择“类型”,面向对象编程用户选择“接口”

  • 您不能在接口上拥有计算或计算属性,而是在类型中。

     type Fullname = "name" | "lastname"
    type  Person= {[key in Keys]: string}
    const me: Person = {firstname: "Yilmaz",lastname: "Bingol"}

演示递归重写对象文字类型和接口而不是类成员/属性/函数的能力。

还有如何区分和键入检查差异和解决上面讨论的问题,当Records由于接口和类似的东西而不起作用时,您可以解决它。这将允许对猫鼬类型进行以下简化:https://github.com/wesleyolis/mongooseRelationalTypes关系类型,深度填充

此外,还有一堆其他方法来进行高级类型泛型和类型推断,以及围绕它的速度怪癖,所有小技巧都可以从许多实验中获得正确的结果,试验和错误。

打字稿游乐场:点击这里查看实时游乐场中的所有示例

    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: Datee: TestC,f: TestD,p: [number],neastedA: {d: string,e: numberh: Date,j: TestCneastedB: {d: string,e: numberh: 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 == numberconst 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 interfacestype 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: TKindevents: TEventsnoEvent: 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:1TArgs: [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.EventAbreak;case 'KindTB':test.EventBbreak;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.
})

根据我最近看到或参与的所有讨论,类型和接口之间的主要区别在于接口可以扩展,而类型不能。

另外,如果你声明一个接口两次,它们将被合并到一个接口中。你不能用类型来做。

来自官方文档

类型别名和接口之间的区别类型别名和接口非常相似,在许多情况下你可以自由地在它们之间进行选择。几乎接口的所有功能都可以在类型中使用,关键区别在于不能重新打开类型以添加新属性,而接口总是可扩展的。

接口是专门为描述对象形状而设计的;然而,类型在某种程度上类似于可用于为任何类型创建新名称的接口。

我们可以说接口可以通过多次声明来扩展;而类型是关闭的。

https://itnext.io/interfaces-vs-types-in-typescript-cf5758211910

更新2022-

类型别名和接口非常相似,在许多情况下可以在它们之间自由选择。界面的几乎所有功能在类型中可用,关键的区别是类型不能是重新打开以添加新属性,而不是总是可扩展。

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces

想添加我的2美分;

我曾经是“界面爱好者”(喜欢interfacetype,除了联合,交叉点等)…直到我开始使用类型“任何键值对象”,也就是Record<string, unknown>

如果您键入“任何键值对象”:

function foo(data: Record<string, unknown>): void {for (const [key, value] of Object.entries(data)) {// whatever}}

如果你使用interface,你可能会进入死胡同

interface IGoo {iam: string;}
function getGoo(): IGoo {return { iam: 'an interface' };}
const goo = getGoo();
foo(goo); // ERROR// Argument of type 'IGoo' is not assignable to parameter of type// 'Record<string, unknown>'.//  Index signature for type 'string' is missing in type// 'IGoo'.ts(2345)

type只是像魅力一样工作:

type Hoo = {iam: string;};
function getHoo(): Hoo {return { iam: 'a type' };}
const hoo = getHoo();
foo(hoo); // works

这个特殊的用例——IMO——发挥了作用。