[英]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
来引用键为A
、 B
和C
的对象。
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
:最大的是class
和enum
:
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.这里,类型Class
是Class
实例的类型,而值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
是一个对象,其键是A
和B
,其属性是枚举的元素。 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:回顾:
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!
};
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!
};
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.