在编译时获取泛型的名称

Obtain the name of a Generic on compile time

问题

有没有办法做到这一点:

type Example<T> = { [nameof T]: T }
                     ^^^^^^

例如:

class MyClass {}

// So it would look like (In compile-time):
Example<MyClass> = { 'MyClass': MyClass } 

动机

我之所以要这样做,是因为我正在使用 Sequelize,Sequelize 在做多对多关系时会自动创建对 Cross table 的引用。例如:

// Structure:
// User -- UserRole -- Role

// User object:
const user = User.find(...)
user.role[0].UserRole // <-- This object is based on the name of the Table

所以我的想法是有一个简单的 Mixin 类型,我可以用它来表示:

class User {
    // Now role can now it has this property!
    role: Array<CrossReferenceOf<Role, UserRole>>
}

TL;DR: 不,没有好的方法。


在您的示例中,名为 MyClass 的类型是提供给 MyClass 实例 的接口。由于您的示例代码没有定义属性或方法,因此它是 equivalent 到空对象类型 {}MyClass 没有“名字” 属性 可言。所以没有办法编写 Example<MyClass> 并让编译器从中生成 {MyClass: MyClass}


您可能希望编译器可以查看名称 MyClass 并将其转换为 string literal "MyClass", but that isn't the way the TS type system works. TypeScript's type system is structural and not nominal。如果 TypeScript 中的两个类型具有相同的结构(相同类型的属性和方法),则它们是相同的类型。另一方面,仅仅因为 TypeScript 中的两种类型由不同的 names 引用或具有不同的 declarations,并不意味着它们不同类型:

interface AlsoMyClass { }
var x: MyClass;
var x: AlsoMyClass; // <-- no error on redeclaration

在这里您可以看到 x 变量被重新声明,没有错误。这表明编译器将 MyClassAlsoMyClass 视为同一类型(尝试将其中一个更改为具有不同结构的类型,第二个声明将导致编译器错误)。

所以如果 Example<MyClass> 可以产生 {"MyClass": MyClass},那么 Example<AlsoMyClass> 必须产生 {"MyClass": MyClass}不是{"AlsoMyClass": MyClass}。至少在 tsc 编译器中,没有原则性的方法来处理 TypeScript 中赋予类型的名称。


人们可能还希望不要引用 MyClass instance 类型,而是引用 MyClass 的类型]构造函数。毕竟,在运行时,MyClass value 确实有一个 name 属性 should 相等至 "MyClass"。所以也许你可以使用 Example<typeof MyClass>,而不是 Example<MyClass>。如果是这样,您可以这样定义 Example

type Example<T extends { name: string; new(...args: any): any }> =
    Record<T['name'], InstanceType<T>>;

不幸的是,这也不是真的。编译器仅将 MyClass 构造函数的 name 属性 视为类型 string:

const myClassName = MyClass.name; // string

所以 Example<typeof MyClass> 就是 {[k: string]: MyClass};不够好。

有一个悬而未决的问题,microsoft/TypeScript#32527 要求编​​译器将字符串文字 name 属性赋予具有已知名称的函数(例如 class 构造函数),但它没有看起来它会发生(特别是因为 name 属性 如果你缩小代码可能不是你所期望的)。

你可以尝试MyClass一个static name属性,但是编译器也不喜欢那样,你需要使用 //@ts-ignore:

抑制警告
class MyClass {
    //@ts-ignore 
    declare static readonly name: "MyClass";
}

Example<typeof MyClass> 中的这个“有效”是 {MyClass: MyClass},但它很难看并且无法缩放。


所以不幸的是我会说“不,这里没什么好东西”。

Playground link to code