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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/50376977/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 05:25:03  来源:igfitidea点击:

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

typescriptgenericsenums

提问by Stephan G

Consider the following typescript enum:

考虑以下打字稿枚举:

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?

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

回答by jcalz

No, the consumer will need to use typeof MyEnumto 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. The syntax of TypeScript is such that some expressions and statements refer to valuesthat exist at runtime, while other expressions and statements refer to typesthat exist only at design/compile time. Values havetypes, 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.

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

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 nullin the following code:

编译器不关心或混淆表达式是否可以同时解释为值和类型。例如,null在以下代码中使用 的两种风格非常满意:

let maybeString: string | null = null;

The first instance of nullis a type and the second is a value. It also has no problem with

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

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

where the first Foois a named value and the second Foois a named type. Note that the type of the value Foois {a: number}, while the type Foois {b: string}. They are not the same.

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

Even the typeofoperator leads a double life. The expression typeof xalways expects xto be a value, but typeof xitself could be a value or type depending on the context:

typeof经营者都过着双重生活。 表达式typeof x总是期望x是一个 value,但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;will make it through to the JavaScript, and it will use the JavaScript typeof operatorat runtime and produce a string. But type TypeofBar = typeof bar; is erased, and it is using the TypeScript type query operatorto examine the static type that TypeScript has assigned to the value named bar.

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



Now, most language constructs in TypeScript that introduce names create either a named value or a named type. Here are some introductions of named values:

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

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 botha named value anda named type, and, like Fooabove, the type of the named value is not the named type. The big ones are classand enum:

但也有其创建一些声明名为值命名类型,并且,像Foo上面的命名值的类型不是指定的类型。大的是classenum

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

Here, the typeClassis the type of an instanceof Class, while the valueClassis the constructorobject. And typeof Classis not Class:

这里,类型Class是类型实例Class,而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 typeEnumis the type of an elementof the enumeration; a union of the types of each element. While the valueEnumis an objectwhose keys are Aand B, and whose properties are the elements of the enumeration. And typeof Enumis not Enum:

并且,类型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}


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 typeEnumin, and get the keys of the objectEnumout. But as you see above, the type Enumis not the same as the object Enum. And unfortunately the type doesn't know anything about the value. It is sort of like saying this:

在那里你把类型Enum的,并得到的钥匙对象Enum了。但是正如你在上面看到的,类型Enum与对象不同Enum。不幸的是,该类型对值一无所知。这有点像这样说:

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 | 1which would produce the type "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...

很明显,如果你这样写,你会发现你无法对0 | 1产生 type的类型做任何事情"A" | "B"。要使其工作,您需要向它传递一个知道映射的类型。而那种类型是typeof Enum...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

which is like

这就像

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

which ispossible... if type EnumKeysAsString<T> = keyof T.

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



So you are stuck making the consumer specify typeof Enum. Are there workarounds? Well, you could maybe use something that does that a value, such as a function?

所以你被困在让消费者指定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)]; 
 }

Then you can call

然后你可以打电话

 const someKey = enumKeysAsString(Enum);

and the type of someKeywill be "A" | "B". Yeah but then to use it as typeyou'd have to query it:

someKeywill的类型"A" | "B"。是的,但是要将它用作类型,您必须查询它:

 type KeysOfEnum = typeof someKey;

which forces you to use typeofagain 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;
  • STILL NOT POSSIBLE;
  • SORRY.
  • 这不可能;
  • 类型和值等等;
  • 仍然不可能;
  • 对不起。

Hope that makes some sense. Good luck.

希望这是有道理的。祝你好运。

回答by Akxe

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 enumtoo!
  • 这工作const enum太!