从类型生成数组而不推断

Generate array from type without infering

我需要根据TS类型生成真正的JS代码,这样我就可以防止重复代码和造成混乱。

export interface TypesInfo {
    mysqlDatabases:    "db1" | "db2" | "db9";
}

//

export class Service {
    protected static $SetInfo() {
        return {
/*instead of true, it has to be TypesInfo.mysqlDatabases converted to array, so ["db1", "db2", "db9"];*/
            mysql: true, // ["db1", "db2", "db9"] instead of true
            mongodb: true
        };
    }
}

从数组推断类型对我不起作用,因为你不能将数组放在接口中,因为接口不是运行时的。

基本上 TypesInfo.mysqlDatabase type 也应该作为启动器,而不仅仅是用于类型检查。

Infering the type from array won't work for me, because you can't put array inside interface, because interface isn't runtime.

您可以创建一个在运行时可用的枚举,而不是使用 mysqlDatabases: "db1" | "db2" | "db9";

export interface TypesInfo extends BaseServiceTypesInfo {
    mysqlDatabases: YourTypeEnum ;
}

enum YourTypeEnum {
    db1 = "db1",
    db2 = "db2",
    db9 = "db9",
}
...
mysql = Object.values(YourTypeEnum);

您希望避免指定数组值 mysql 及其元素类型 mysqlDatabasesunion type 的冗余,因此您要求一种方法来生成来自联合类型的数组值。

不幸的是,这是不可能的;当 TypeScript 被编译为 JavaScript 时,TypesInfo 接口和所有接口一样,是 erased,因此没有任何东西可以用来构建你想要的数组。您无法从 TypeScript 类型生成 JavaScript 值。

不过你也可以用其他方式做事。所以这里避免冗余的唯一可能方法是从 JavaScript 值 ["db1", "db2", "db9"].

生成 TypeScript 类型 "db1" | "db2" | "db9"

如果您的 Service.$SetInfo() 静态方法是 public 而不是 protected,那么最不冗余的方法是在 return 中显式定义数组值的 mysql 属性:

export class Service {
    static $SetInfo() {
        return {
            mysql: ["db1", "db2", "db9"] as const,
            mongodb: true
        };
    }
}

在这里(就像在所有此类解决方案中一样)我们使用其元素的 const assertion to ask the compiler to remember the literal types

然后,您可以根据 $SetInfo 方法定义 TypesInfomysqlDatabases 属性:

export interface TypesInfo {
    mysqlDatabases: ReturnType<typeof Service.$SetInfo>['mysql'][number];
    // (property) AllPublic.TypesInfo.mysqlDatabases: "db1" | "db2" | "db9"
}

这可行,但它 Service.$SetInfo 暴露给外部消费者。


如果您想将 Service.$SetInfo 保留为 protected 方法并且您不想公开任何其他内容,那么我认为唯一合理的方法是定义数组在你的模块中而不导出它,然后根据这个模块私有值定义 TypesInfo$SetInfo

const mysqlDatabases = ["db1", "db2", "db9"] as const;

export interface TypesInfo {
    mysqlDatabases: typeof mysqlDatabases[number];
}

export class Service {
    protected static $SetInfo() {
        return {
            mysql: mysqlDatabases,
            mongodb: true
        };
    }
}

这确实在您的模块中添加了另一个声明,并且您在评论中明确表示这是不可接受的。但我想展示它,因为它在包范围方面与您的原始代码非常相似。


您可以接受的中间立场是公开公开数组而不是整个 $SetInfo 方法,方法是使数组成为 Service 的单独静态 属性:

export class Service {
    static mysqlDatabases = ["db1", "db2", "db9"] as const;
    protected static $SetInfo() {
        return {
            mysql: Service.mysqlDatabases,
            mongodb: true
        };
    }
}

export interface TypesInfo {
    mysqlDatabases: typeof Service.mysqlDatabases[number];
}

我个人更喜欢三声明解决方案,因为似乎没有充分的理由将 mySqlDatabases 数组暴露给 Service 的外部用户,但如果你真的很看重只有两个模块中的声明,那么这可能是一个很好的折衷方案。


Playground link to code