简体   繁体   English

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

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

Consider the following typescript enum:考虑以下 typescript 枚举:

enum MyEnum { A, B, C };

If I want another type that is the unioned strings of the keys of that enum, I can do the following:如果我想要另一种类型,即该枚举的键的联合字符串,我可以执行以下操作:

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

This is very useful.这非常有用。

Now I want to create a generic type that operates universally on enums in this way, so that I can instead say:现在我想创建一个通用类型,以这种方式对枚举进行通用操作,这样我就可以说:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

I imagine the correct syntax for that would be:我想正确的语法是:

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

But that generates a compile error: "'TEnum' only refers to a type, but is being used as a value here."但这会产生一个编译错误:“'TEnum' 只引用一个类型,但在这里被用作一个值。”

This is unexpected and sad.这是出乎意料和悲哀的。 I can incompletely work around it the following way by dropping the typeof from the right side of the declaration of the generic, and adding it to the type parameter in the declaration of the specific type:我可以通过从泛型声明的右侧删除 typeof 并将其添加到特定类型声明中的类型参数来通过以下方式不完全解决它:

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

I don't like this workaround though, because it means the consumer has to remember to do this icky specifying of typeof on the generic.不过,我不喜欢这种解决方法,因为这意味着消费者必须记住对泛型进行这种讨厌的 typeof 指定。

Is there any syntax that will allow me to specify the generic type as I initially want, to be kind to the consumer?是否有任何语法可以让我按照最初的意愿指定泛型类型,以善待消费者?

No, the consumer will need to use typeof MyEnum to refer to the object whose keys are A , B , and C .不,消费者需要使用typeof MyEnum来引用键为ABC的对象。


LONG EXPLANATION AHEAD, SOME OF WHICH YOU PROBABLY ALREADY KNOW前面有很长的解释,其中一些你可能已经知道了

As you are likely aware, TypeScript adds a static type system to JavaScript, and that type system gets erased when the code is transpiled.您可能知道,TypeScript 向 JavaScript 添加了一个静态类型系统,并且在代码被转译时该类型系统会被删除 The syntax of TypeScript is such that some expressions and statements refer to values that exist at runtime, while other expressions and statements refer to types that exist only at design/compile time. TypeScript 的语法是这样的,一些表达式和语句引用运行时存在的,而其他表达式和语句引用仅在设计/编译时存在的类型 Values have types, but they are not types themselves.具有类型,但它们本身不是类型。 Importantly, there are some places in the code where the compiler will expect a value and interpret the expression it finds as a value if possible, and other places where the compiler will expect a type and interpret the expression it finds as a type if possible.重要的是,在代码中的某些地方,编译器会期望一个值并将它找到的表达式解释为一个值,如果可能的话,编译器会期望一个类型并将它找到的表达式解释为一个类型。

The compiler does not care or get confused if it is possible for an expression to be interpreted as both a value and a type.如果一个表达式可能被解释为一个值和一个类型,编译器不会在意或感到困惑。 It is perfectly happy, for instance, with the two flavors of null in the following code:例如,对于以下代码中的两种类型的null ,它非常高兴:

let maybeString: string | null = null;

The first instance of null is a type and the second is a value. null的第一个实例是一个类型,第二个是一个值。 It also has no problem with它也没有问题

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

where the first Foo is a named value and the second Foo is a named type.其中第一个Foo是一个命名值,第二个Foo是一个命名类型。 Note that the type of the value Foo is {a: number} , while the type Foo is {b: string} .请注意,值Foo的类型是{a: number} ,而类型Foo{b: string} They are not the same.他们不一样。

Even the typeof operator leads a double life.即使是typeof运算符也有双重的生活。 The expression typeof x always expects x to be a value , but typeof x itself could be a value or type depending on the context:表达式typeof x总是期望x是一个值,但typeof x本身可以是一个值或类型,具体取决于上下文:

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

The line let TypeofBar = typeof bar;let TypeofBar = typeof bar; will make it through to the JavaScript, and it will use the JavaScript typeof operator at runtime and produce a string.将通过 JavaScript,它会在运行时使用JavaScript typeof 运算符并生成一个字符串。 But type TypeofBar = typeof bar ;但是type TypeofBar = typeof bar ; is erased, and it is using the TypeScript type query operator to examine the static type that TypeScript has assigned to the value named bar .被删除,它使用TypeScript 类型查询运算符来检查 TypeScript 分配给名为bar的值的静态类型。


Now, most language constructs in TypeScript that introduce names create either a named value or a named type.现在,TypeScript 中大多数引入名称的语言结构都创建了命名值或命名类型。 Here are some introductions of named values:下面是一些命名值的介绍:

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

And here are some introductions of named types:以下是命名类型的一些介绍:

interface Type1 {}
type Type2 = string;

But there are a few declarations which create both a named value and a named type, and, like Foo above, the type of the named value is not the named type .但是有一些声明同时创建命名值命名类型,并且像上面的Foo一样,命名值的类型不是命名类型 The big ones are class and enum :最大的是classenum

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

Here, the type Class is the type of an instance of Class , while the value Class is the constructor object.这里,类型ClassClass实例的类型,而Class构造函数对象。 And typeof Class is not 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;

And, the type Enum is the type of an element of the enumeration;并且, Enum类型是枚举元素的类型; a union of the types of each element.每个元素的类型的联合。 While the value Enum is an object whose keys are A and B , and whose properties are the elements of the enumeration.Enum是一个对象,其键是AB ,其属性是枚举的元素。 And typeof Enum is not Enum :并且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}

Backing way way up to your question now.现在支持您的问题。 You want to invent a type operator that works like this:你想发明一个像这样工作的类型运算符:

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

where you put the type Enum in, and get the keys of the object Enum out.类型Enum放入其中,然后取出对象Enum的键。 But as you see above, the type Enum is not the same as the object Enum .但正如您在上面看到的,类型Enum与对象Enum不同 And unfortunately the type doesn't know anything about the value.不幸的是,该类型对值一无所知。 It is sort of like saying this:这有点像这样说:

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

Clearly if you write it like that, you'd see that there's nothing you could do to the type 0 | 1显然,如果你这样写,你会发现你对类型0 | 1无能为力。 0 | 1 which would produce the type "A" | "B" 0 | 1将产生类型"A" | "B" "A" | "B" . "A" | "B" To make it work, you'd need to pass it a type that knows about the mapping.为了让它工作,你需要传递一个知道映射的类型。 And that type is typeof Enum ...那个类型是typeof Enum ...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

which is like这就像

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

which is possible... if type EnumKeysAsString<T> = keyof T .可能的......如果type EnumKeysAsString<T> = keyof T


So you are stuck making the consumer specify typeof Enum .所以你坚持让消费者指定typeof Enum Are there workarounds?有解决方法吗? Well, you could maybe use something that does that a value, such as a function?好吧,您也许可以使用具有该值的东西,例如函数?

 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)]; 
 }

Then you can call然后你可以打电话

 const someKey = enumKeysAsString(Enum);

and the type of someKey will be "A" | "B"并且someKey的类型将是"A" | "B" "A" | "B" . "A" | "B" Yeah but then to use it as type you'd have to query it:是的,但是要使用它作为类型,你必须查询它:

 type KeysOfEnum = typeof someKey;

which forces you to use typeof again and is even more verbose than your solution, especially since you can't do this:这迫使您再次使用typeof并且比您的解决方案更冗长,特别是因为您不能这样做:

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

Blegh.布莱。 Sorry.对不起。


TO RECAP:回顾:

  • THIS IS NOT POSSIBLE;这是不可能的;
  • TYPES AND VALUES BLAH BLAH;类型和价值 blah blah;
  • STILL NOT POSSIBLE;仍然不可能;
  • SORRY.对不起。

Hope that makes some sense.希望这有点道理。 Good luck.祝你好运。

It actually is possible.这实际上是可能的。

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!
};

Playground Link 游乐场链接


EDIT: Lifted limitation!编辑:解除限制!

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!
};

Playground Link 游乐场链接


  • This work with const enum too!这也适用于const enum

This is solution that doesn't require to create new generic types.这是不需要创建新的泛型类型的解决方案。

If you declare an enum如果你声明一个枚举

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

To get to the type you only need to use the keywords keyof typeof要获得类型,您只需要使用关键字keyof typeof

let seasonKey: keyof typeof Season;

Then the variable works as expected然后变量按预期工作

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

You can just pass a type instead of a value and the compiler won't complain.你可以只传递一个type而不是一个value ,编译器不会抱怨。 This you achieve with typeof as you pointed out.正如您所指出的,您可以通过typeof实现这一点。
Will be just a bit less automatic:将不那么自动化:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;

Which you can use as:您可以将其用作:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;

If I understand the OP question correctly and Akxe answer: Here is a possible further simplification.如果我正确理解了 OP 问题并且 Akxe 回答:这是一个可能的进一步简化。 Use the typescript type utility.使用 typescript 类型的实用程序。 Record<Keys, Type>记录<键,类型>

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

eg例如

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