在打字稿中获取枚举键作为联合字符串的通用类型? [英] Generic type to get enum keys as union string in typescript?

查看:35
本文介绍了在打字稿中获取枚举键作为联合字符串的通用类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下打字稿枚举:

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"

这很有用.

现在我想创建一个通用类型,以这种方式在枚举上通用,所以我可以说:

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

我想正确的语法是:

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

但这会产生编译错误:'TEnum' 仅指代一种类型,但在此处用作值."

But that generates a compile error: "'TEnum' only refers to a type, but is being used as a value here."

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

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:

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

不过我不喜欢这种变通方法,因为这意味着消费者必须记住在泛型上执行这种令人讨厌的 typeof 指定.

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.

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

Is there any syntax that will allow me to specify the generic type as I initially want, to be kind to the consumer?

推荐答案

否,消费者将需要使用 typeof MyEnum 来引用键为 A 的对象、BC.

No, the consumer will need to use typeof MyEnum to refer to the object whose keys are A, B, and C.

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

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

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 values that exist at runtime, while other expressions and statements refer to types that exist only at design/compile time. 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.

编译器并不关心或混淆表达式是否可以同时被解释为值和类型.例如,对于 null 在以下代码中:

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:

let maybeString: string | null = null;

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

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

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

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

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

即使是 typeof 运算符也有双重生活.表达式 typeof x 总是期望 x 是一个值,但 typeof x 本身可以是一个值或类型视上下文而定:

Even the typeof operator leads a double life. 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:

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 operator 在运行时生成一个字符串.但是type TypeofBar = typeof bar;被删除,它使用 TypeScript 类型查询运算符检查TypeScript分配给名为bar的值的静态类型.

The line 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. But 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 语言构造都会创建命名值或命名类型.下面是对命名值的一些介绍:

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:

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

这里是一些命名类型的介绍:

And here are some introductions of named types:

interface Type1 {}
type Type2 = string;

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

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. The big ones are class and enum:

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

这里的 type ClassClassinstance 的类型,而 value Classconstructor 对象.而 typeof Class 不是 Class:

Here, the type Class is the type of an instance of Class, while the value Class is the constructor object. And typeof Class is not 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;

而且,type Enum 是枚举的元素的类型;每个元素的类型的联合.而 value Enum 是一个 object,其键是 AB,并且其属性是枚举的元素.而 typeof Enum 不是 Enum:

And, the type Enum is the type of an element of the enumeration; 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. And typeof Enum is not 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"

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

where you put the type Enum in, and get the keys of the object Enum out. But as you see above, the type Enum is 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:

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

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

Clearly if you write it like that, you'd see that there's nothing you could do to the type 0 | 1 which 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...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

就像

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

哪个可能的...如果type EnumKeysAsString;= keyof T.

which is possible... if type EnumKeysAsString<T> = keyof T.

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

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?

 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".是的,但是要将它用作类型,您必须查询它:

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

 type KeysOfEnum = typeof someKey;

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

which forces you to use typeof again and is even more verbose than your solution, especially since you can't do this:

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

布莱格.对不起.

回顾:

  • 这不可能;
  • 类型和值等等;
  • 仍然不可能;
  • 对不起.

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

Hope that makes some sense. Good luck.

这篇关于在打字稿中获取枚举键作为联合字符串的通用类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆