考虑以下打字稿枚举:
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'仅是指一种类型,但在这里被用作价值。”
这是出乎意料和悲伤的。通过从通用声明声明的右侧删除类型,并将其添加到特定类型的声明中的类型参数:
type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer. Ick.
我不喜欢这种解决方法,因为它可以使其围绕以下方式进行以下操作:因为它这意味着消费者必须记住要在通用中指定类型的iCky。
是否有任何语法可以让我按照我最初想要的通用类型来指定对消费者的友好类型?
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."
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.
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?
发布评论
评论(5)
不,消费者将需要使用
typeof myenum
来参考密钥为a
,b
和c
c 的对象。代码>。漫长的解释未来,您可能已经知道的一些可能已经知道
typescript在JavaScript中添加了静态类型系统,并且该类型系统获得 value ,而其他表达式和语句则是指仅在设计/编译时间上存在的 type 。值具有类型,但它们本身不是类型。重要的是,编译器在代码中有些地方期望一个值并解释其在值时发现的表达式,而其他位置则在编译器期望类型并解释其在可能的情况下将其解释为一种类型的表达式。
编译器如果可以将表达式解释为一个值和类型。,它是完全高兴的在以下代码中:
null
的第一个实例是一种类型,第二个是值。 也没有问题对于第一个
foo
是命名值,第二个foo
是命名类型 。请注意,值的类型foo
是{a:numbers}
,而类型foo
isis
{b:string} < /代码>。他们不一样。
甚至
typeof
运营商都会带有双重寿命。 表达式X型X
总是期望x
是一个值,但是type> type x
本身可能是一个值或类型取决于上下文:LINE
Let typeofbar = typeOf bar;
将使它贯穿到JavaScript,并且它将使用 javascript typeof操作员在运行时并产生一个字符串。但是type typeofbar = typeof bar
;被删除,它正在使用检查打字稿已分配给名称bar
的值的静态类型。现在,引入名称的打字稿中的大多数语言构造创建命名值或命名类型。以下是命名值的一些介绍:
以下是一些命名类型的介绍:
但是有一些声明会创建同时像
foo
上面,命名值的类型不是命名类型。大个子是类
和class
的,而 valueclass
是构造函数对象。类型类
不是class
:和, type
enum
是 element element 的类型/em>枚举;每个元素类型的结合。而 valueenum
是对象,其密钥是a
和b
,并且其特性是枚举的要素。unum
不是enum
:备份到您的问题。您想发明一个类型的运算符,以这样的工作:
将 type
enum
放入其中,并获取 object枚举 out。但是如上所述,类型
enum
与对象enum
不同。不幸的是,该类型对该值一无所知。这有点像这样说:清楚地说,如果您这样写,您会发现对
0 |的类型无能为力。 1
将产生类型“ a” | “ B”
。为了使它起作用,您需要将其传递给知道映射的类型。该类型是typeof enum
...,就像
是可能...如果
type type enumkeysassstring&lt; t&gt; = temof t
。因此,您将消费者指定
typeof Enum
。有解决方法吗?好吧,您可能可以使用具有值得一个值的东西,例如函数?然后,您可以致电
,
somekey
的类型将为“ a” | “ B”
。是的,但是要将其用作 type ,您必须查询它:它迫使您再次使用
typeof
,并且比解决方案更详细,尤其是因为您可以't这样做:blegh。对不起。
回顾一下:
No, the consumer will need to use
typeof MyEnum
to refer to the object whose keys areA
,B
, andC
.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 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.
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:The first instance of
null
is a type and the second is a value. It also has no problem withwhere the first
Foo
is a named value and the secondFoo
is a named type. Note that the type of the valueFoo
is{a: number}
, while the typeFoo
is{b: string}
. They are not the same.Even the
typeof
operator leads a double life. The expressiontypeof x
always expectsx
to be a value, buttypeof x
itself could be a value or type depending on the context: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. Buttype 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 namedbar
.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:
And here are some introductions of named types:
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 areclass
andenum
:Here, the type
Class
is the type of an instance ofClass
, while the valueClass
is the constructor object. Andtypeof Class
is notClass
:And, the type
Enum
is the type of an element of the enumeration; a union of the types of each element. While the valueEnum
is an object whose keys areA
andB
, and whose properties are the elements of the enumeration. Andtypeof Enum
is notEnum
:Backing way way up to your question now. You want to invent a type operator that works like this:
where you put the type
Enum
in, and get the keys of the objectEnum
out. But as you see above, the typeEnum
is not the same as the objectEnum
. And unfortunately the type doesn't know anything about the value. It is sort of like saying this: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 istypeof Enum
...which is like
which is possible... if
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?Then you can call
and the type of
someKey
will be"A" | "B"
. Yeah but then to use it as type you'd have to query it:which forces you to use
typeof
again and is even more verbose than your solution, especially since you can't do this:Blegh. Sorry.
To recap:
较短,更简单的解决方案
创建新的通用类型并不需要。
如果您声明枚举
即可到达类型,则只需要使用关键字
keyof
typef
,则变量按预期工作
Shorter, simpler solution
It is not required to create new generic types.
If you declare an enum
To get to the type you only need to use the keywords
keyof
typeof
Then the variable works as expected
实际上是可能的。
Playground Link
EDIT: Lifted limitation!
Playground Link
const enum
也!It actually is possible.
Playground Link
EDIT: Lifted limitation!
Playground Link
const enum
too!您只需传递
type
而不是value
,并且编译器不会抱怨。您指出的是typeof
实现的。将少一些自动:
您可以使用为:
You can just pass a
type
instead of avalue
and the compiler won't complain. This you achieve withtypeof
as you pointed out.Will be just a bit less automatic:
Which you can use as:
如果我正确理解OP问题,并且AKXE答案:这是可能进一步简化的。使用打字稿类型实用程序。记录&lt;键,type&gt;
例如
If I understand the OP question correctly and Akxe answer: Here is a possible further simplification. Use the typescript type utility. Record<Keys, Type>
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
e.g.