通用类型将枚举键作为打字稿中的联合字符串?

发布于 2025-01-26 23:32:49 字数 949 浏览 3 评论 0 原文

考虑以下打字稿枚举:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

ˉ厌 2025-02-02 23:32:49

不,消费者将需要使用 typeof myenum 来参考密钥为 a b c c 的对象。代码>。


漫长的解释未来,您可能已经知道的一些可能已经知道

typescript在JavaScript中添加了静态类型系统,并且该类型系统获得 value ,而其他表达式和语句则是指仅在设计/编译时间上存在的 type 。值具有类型,但它们本身不是类型。重要的是,编译器在代码中有些地方期望一个值并解释其在值时发现的表达式,而其他位置则在编译器期望类型并解释其在可能的情况下将其解释为一种类型的表达式。

编译器如果可以将表达式解释为一个值和类型。,它是完全高兴的在以下代码中:

let maybeString: string | null = null;

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

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

对于第一个 foo 是命名值,第二个 foo 是命名类型 。请注意,值的类型 foo {a:numbers} ,而类型 foo is is {b:string} < /代码>。他们不一样。

甚至 typeof 运营商都会带有双重寿命。 表达式 X型X 总是期望 x 是一个值,但是 type> type x 本身可能是一个值或类型取决于上下文:

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

LINE Let typeofbar = typeOf bar; 将使它贯穿到JavaScript,并且它将使用 javascript typeof操作员在运行时并产生一个字符串。但是 type typeofbar = typeof bar ;被删除,它正在使用检查打字稿已分配给名称 bar 的值的静态类型。


现在,引入名称的打字稿中的大多数语言构造创建命名值或命名类型。以下是命名值的一些介绍:

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

以下是一些命名类型的介绍:

interface Type1 {}
type Type2 = string;

但是有一些声明会创建同时像 foo 上面,命名值的类型不是命名类型。大个子是

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

class 的,而 value 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;

和, type enum element element 的类型/em>枚举;每个元素类型的结合。而 value enum 对象,其密钥是 a b ,并且其特性是枚举的要素。 unum 不是 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}

备份到您的问题。您想发明一个类型的运算符,以这样的工作:

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

type enum 放入其中,并获取 object 枚举 out。但是如上所述,类型 enum 与对象 enum 不同。不幸的是,该类型对该值一无所知。这有点像这样说:

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

清楚地说,如果您这样写,您会发现对 0 |的类型无能为力。 1 将产生类型“ a” | “ B” 。为了使它起作用,您需要将其传递给知道映射的类型。该类型是 typeof enum ...

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

,就像

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

可能...如果 type type enumkeysassstring&lt; t&gt; = temof t


因此,您将消费者指定 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)]; 
 }

然后,您可以致电

 const someKey = enumKeysAsString(Enum);

somekey 的类型将为“ a” | “ B” 。是的,但是要将其用作 type ,您必须查询它:

 type KeysOfEnum = typeof someKey;

它迫使您再次使用 typeof ,并且比解决方案更详细,尤其是因为您可以't这样做:

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

blegh。对不起。


回顾一下:

  • 不可能的
  • 类型和价值,等等
  • 是不可能的
  • 这是

No, the consumer will need to use typeof MyEnum to refer to the object whose keys are A, B, and 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. 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:

let maybeString: string | 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};   

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.

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}

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.


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;

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 }

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;

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"

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"

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

which is like

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

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?

 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". 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:

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

Blegh. Sorry.


To recap:

  • This is not possible
  • Types and values blah blah
  • Still not possible
  • Sorry
〃温暖了心ぐ 2025-02-02 23:32:49

较短,更简单的解决方案

创建新的通用类型并不需要。

如果您声明枚举

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

即可到达类型,则只需要使用关键字 keyof typef

let seasonKey: keyof typeof Season;

,则变量按预期工作

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

Shorter, simpler solution

It is not required 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

let seasonKey: keyof typeof Season;

Then the variable works as expected

seasonKey = "Autumn"; // is fine
// seasonKey = "AA" <= won't compile
剪不断理还乱 2025-02-02 23:32:49

实际上是可能的。

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也!

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!
撧情箌佬 2025-02-02 23:32:49

您只需传递 type 而不是 value ,并且编译器不会抱怨。您指出的是 typeof 实现的。
将少一些自动:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;

您可以使用为:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;

You can just pass a type instead of a value and the compiler won't complain. This you achieve with typeof as you pointed out.
Will be just a bit less automatic:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;

Which you can use as:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;
超可爱的懒熊 2025-02-02 23:32:49

如果我正确理解OP问题,并且AKXE答案:这是可能进一步简化的。使用打字稿类型实用程序。记录&lt;键,type&gt;

例如

enum MyEnum { A, B, C };

type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = Record<enumValues, string>
const a: ObjectWithKeysOfEnumAsKeys = {
    A: "PropertyA",
    B: "PropertyB",
    C: "PropertyC",
};

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.

enum MyEnum { A, B, C };

type enumValues = keyof typeof MyEnum;
type ObjectWithKeysOfEnumAsKeys = Record<enumValues, string>
const a: ObjectWithKeysOfEnumAsKeys = {
    A: "PropertyA",
    B: "PropertyB",
    C: "PropertyC",
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文