繁体   English   中英

在 typescript 中获取枚举键作为联合字符串的通用类型?

[英]Generic type to get enum keys as union string in typescript?

考虑以下 typescript 枚举:

enum MyEnum { A, B, C };

如果我想要另一种类型,即该枚举的键的联合字符串,我可以执行以下操作:

type MyEnumKeysAsStrings = keyof typeof MyEnum;  // "A" | "B" | "C"

这非常有用。

现在我想创建一个通用类型,以这种方式对枚举进行通用操作,这样我就可以说:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

我想正确的语法是:

type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: 'TEnum' only refers to a type, but is being used as a value here.

但这会产生一个编译错误:“'TEnum' 只引用一个类型,但在这里被用作一个值。”

这是出乎意料和悲哀的。 我可以通过从泛型声明的右侧删除 typeof 并将其添加到特定类型声明中的类型参数来通过以下方式不完全解决它:

type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer.  Ick.

不过,我不喜欢这种解决方法,因为这意味着消费者必须记住对泛型进行这种讨厌的 typeof 指定。

是否有任何语法可以让我按照最初的意愿指定泛型类型,以善待消费者?

不,消费者需要使用typeof MyEnum来引用键为ABC的对象。


前面有很长的解释,其中一些你可能已经知道了

您可能知道,TypeScript 向 JavaScript 添加了一个静态类型系统,并且在代码被转译时该类型系统会被删除 TypeScript 的语法是这样的,一些表达式和语句引用运行时存在的,而其他表达式和语句引用仅在设计/编译时存在的类型 具有类型,但它们本身不是类型。 重要的是,在代码中的某些地方,编译器会期望一个值并将它找到的表达式解释为一个值,如果可能的话,编译器会期望一个类型并将它找到的表达式解释为一个类型。

如果一个表达式可能被解释为一个值和一个类型,编译器不会在意或感到困惑。 例如,对于以下代码中的两种类型的null ,它非常高兴:

let maybeString: string | null = null;

null的第一个实例是一个类型,第二个是一个值。 它也没有问题

let Foo = {a: 0};
type Foo = {b: string};   

其中第一个Foo是一个命名值,第二个Foo是一个命名类型。 请注意,值Foo的类型是{a: number} ,而类型Foo{b: string} 他们不一样。

即使是typeof运算符也有双重的生活。 表达式typeof x总是期望x是一个值,但typeof x本身可以是一个值或类型,具体取决于上下文:

let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}

let TypeofBar = typeof bar; 将通过 JavaScript,它会在运行时使用JavaScript typeof 运算符并生成一个字符串。 但是type TypeofBar = typeof bar ; 被删除,它使用TypeScript 类型查询运算符来检查 TypeScript 分配给名为bar的值的静态类型。


现在,TypeScript 中大多数引入名称的语言结构都创建了命名值或命名类型。 下面是一些命名值的介绍:

const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}

以下是命名类型的一些介绍:

interface Type1 {}
type Type2 = string;

但是有一些声明同时创建命名值命名类型,并且像上面的Foo一样,命名值的类型不是命名类型 最大的是classenum

class Class { public prop = 0; }
enum Enum { A, B }

这里,类型ClassClass实例的类型,而Class构造函数对象。 并且typeof Class不是Class

const instance = new Class();  // value instance has type (Class)
// type (Class) is essentially the same as {prop: number};

const ctor = Class; // value ctor has type (typeof Class)
// type (typeof Class) is essentially the same as new() => Class;

并且, Enum类型是枚举元素的类型; 每个元素的类型的联合。 Enum是一个对象,其键是AB ,其属性是枚举的元素。 并且typeof Enum不是Enum

const element = Math.random() < 0.5 ? Enum.A : Enum.B; 
// value element has type (Enum)
// type (Enum) is essentially the same as Enum.A | Enum.B
//  which is a subtype of (0 | 1)

const enumObject = Enum;
// value enumObject has type (typeof Enum)
// type (typeof Enum) is essentially the same as {A: Enum.A; B: Enum.B}
//  which is a subtype of {A:0, B:1}

现在支持您的问题。 你想发明一个像这样工作的类型运算符:

type KeysOfEnum = EnumKeysAsStrings<Enum>;  // "A" | "B"

类型Enum放入其中,然后取出对象Enum的键。 但正如您在上面看到的,类型Enum与对象Enum不同 不幸的是,该类型对值一无所知。 这有点像这样说:

type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"

显然,如果你这样写,你会发现你对类型0 | 1无能为力。 0 | 1将产生类型"A" | "B" "A" | "B" 为了让它工作,你需要传递一个知道映射的类型。 那个类型是typeof Enum ...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

这就像

type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"

可能的......如果type EnumKeysAsString<T> = keyof T


所以你坚持让消费者指定typeof Enum 有解决方法吗? 好吧,您也许可以使用具有该值的东西,例如函数?

 function enumKeysAsString<TEnum>(theEnum: TEnum): keyof TEnum {
   // eliminate numeric keys
   const keys = Object.keys(theEnum).filter(x => 
     (+x)+"" !== x) as (keyof TEnum)[];
   // return some random key
   return keys[Math.floor(Math.random()*keys.length)]; 
 }

然后你可以打电话

 const someKey = enumKeysAsString(Enum);

并且someKey的类型将是"A" | "B" "A" | "B" 是的,但是要使用它作为类型,你必须查询它:

 type KeysOfEnum = typeof someKey;

这迫使您再次使用typeof并且比您的解决方案更冗长,特别是因为您不能这样做:

 type KeysOfEnum = typeof enumKeysAsString(Enum); // error

布莱。 对不起。


回顾:

  • 这是不可能的;
  • 类型和价值 blah blah;
  • 仍然不可能;
  • 对不起。

希望这有点道理。 祝你好运。

这实际上是可能的。

enum MyEnum { A, B, C };

type ObjectWithValuesOfEnumAsKeys = { [key in MyEnum]: string };

const a: ObjectWithValuesOfEnumAsKeys = {
    "0": "Hello",
    "1": "world",
    "2": "!",
};

const b: ObjectWithValuesOfEnumAsKeys = {
    [MyEnum.A]: "Hello",
    [MyEnum.B]: "world",
    [MyEnum.C]: "!",
};

// Property '2' is missing in type '{ 0: string; 1: string; }' but required in type 'ObjectWithValuesOfEnumAsKeys'.
const c: ObjectWithValuesOfEnumAsKeys = {  //  Invalid! - Error here!
    [MyEnum.A]: "Hello",
    [MyEnum.B]: "world",
};

// Object literal may only specify known properties, and '6' does not exist in type 'ObjectWithValuesOfEnumAsKeys'.
const d: ObjectWithValuesOfEnumAsKeys = {
    [MyEnum.A]: "Hello",
    [MyEnum.B]: "world",
    [MyEnum.C]: "!",
    6: "!",  //  Invalid! - Error here!
};

游乐场链接


编辑:解除限制!

enum MyEnum { A, B, C };

type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = { [key in enumValues]: string };

const a: ObjectWithKeysOfEnumAsKeys = {
    A: "Hello",
    B: "world",
    C: "!",
};

// Property 'C' is missing in type '{ 0: string; 1: string; }' but required in type 'ObjectWithValuesOfEnumAsKeys'.
const c: ObjectWithKeysOfEnumAsKeys = {  //  Invalid! - Error here!
    A: "Hello",
    B: "world",
};

// Object literal may only specify known properties, and '6' does not exist in type 'ObjectWithValuesOfEnumAsKeys'.
const d: ObjectWithKeysOfEnumAsKeys = {
    A: "Hello",
    B: "world",
    C: "!",
    D: "!",  //  Invalid! - Error here!
};

游乐场链接


  • 这也适用于const enum

这是不需要创建新的泛型类型的解决方案。

如果你声明一个枚举

enum Season { Spring, Summer, Autumn, Winter };

要获得类型,您只需要使用关键字keyof typeof

let seasonKey: keyof typeof Season;

然后变量按预期工作

seasonKey = "Autumn"; // is fine
// seasonKey = "AA" <= won't compile

你可以只传递一个type而不是一个value ,编译器不会抱怨。 正如您所指出的,您可以通过typeof实现这一点。
将不那么自动化:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;

您可以将其用作:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;

如果我正确理解了 OP 问题并且 Akxe 回答:这是一个可能的进一步简化。 使用 typescript 类型的实用程序。 记录<键,类型>

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

例如

enum MyEnum { A, B, C };

type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = Record<enumValues, string>
const a: ObjectWithKeysOfEnumAsKeys = {
    A: "PropertyA",
    B: "PropertyB",
    C: "PropertyC",
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM