将通用键作为联合字符串中的联合字符串获取的通用类型?

Ste*_*n G 18 generics enums typescript

请考虑以下typescript枚举:

enum MyEnum { A, B, C };
Run Code Online (Sandbox Code Playgroud)

如果我想要另一种类型是该枚举键的联合字符串,我可以执行以下操作:

type MyEnumKeysAsStrings = keyof typeof MyEnum;  // "A" | "B" | "C"
Run Code Online (Sandbox Code Playgroud)

这非常有用.

现在我想以这种方式创建一个在枚举上普遍运行的泛型类型,这样我就可以说:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;
Run Code Online (Sandbox Code Playgroud)

我想是正确的语法是:

type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: 'TEnum' only refers to a type, but is being used as a value here.
Run Code Online (Sandbox Code Playgroud)

但是这会产生编译错误:"'TEnum'只引用一个类型,但在这里被用作值."

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

type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer.  Ick.
Run Code Online (Sandbox Code Playgroud)

我不喜欢这种解决方法,因为这意味着消费者必须记住在通用上指定typeof.

是否有任何语法允许我指定我最初想要的泛型类型,以便对消费者友好?

jca*_*alz 42

没有,消费者将需要使用typeof MyEnum引用的键是对象A,BC.


前面有很长的解释,你可能已经知道了

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

如果表达式可以被解释为值和类型,则编译器不关心或混淆. 例如,使用null以下代码中的两种风格非常高兴:

let maybeString: string | null = null;
Run Code Online (Sandbox Code Playgroud)

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

let Foo = {a: 0};
type Foo = {b: string};   
Run Code Online (Sandbox Code Playgroud)

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

即使typeof操作员也能过上双重生活. 表达式typeof x总是期望x是一个值,但typeof x它本身可以是一个值或类型,具体取决于上下文:

let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}
Run Code Online (Sandbox Code Playgroud)

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


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

const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}
Run Code Online (Sandbox Code Playgroud)

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

interface Type1 {}
type Type2 = string;
Run Code Online (Sandbox Code Playgroud)

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

class Class { public prop = 0; }
enum Enum { A, B }
Run Code Online (Sandbox Code Playgroud)

这里,类型 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;
Run Code Online (Sandbox Code Playgroud)

并且,类型 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}
Run Code Online (Sandbox Code Playgroud)

现在支持你的问题.您想要发明一个类似于此的类型运算符:

type KeysOfEnum = EnumKeysAsStrings<Enum>;  // "A" | "B"
Run Code Online (Sandbox Code Playgroud)

你把类型 放在哪里Enum,并获取对象 的键Enum.但是如上所述,类型Enum与对象不同Enum.不幸的是,这种类型对价值一无所知.这有点像说:

type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"
Run Code Online (Sandbox Code Playgroud)

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

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 
Run Code Online (Sandbox Code Playgroud)

这就像

type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"
Run Code Online (Sandbox Code Playgroud)

可能的......如果type EnumKeysAsString<T> = keyof 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)]; 
 }
Run Code Online (Sandbox Code Playgroud)

然后你可以打电话

 const someKey = enumKeysAsString(Enum);
Run Code Online (Sandbox Code Playgroud)

和类型someKey将是"A" | "B".是的,但然后将其用作类型,你必须查询它:

 type KeysOfEnum = typeof someKey;
Run Code Online (Sandbox Code Playgroud)

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

 type KeysOfEnum = typeof enumKeysAsString(Enum); // error
Run Code Online (Sandbox Code Playgroud)

Blegh.抱歉.


要收回:

  • 这是不可能的;
  • 类型和价值BLAH BLAH;
  • 仍然不可能;
  • 抱歉.

希望这有点道理.祝好运.

  • 这太棒了,谢谢。为额外的细微教育点赞。这让我认为打字稿可能会受益于某种预处理器,它可以(至少在这种情况下)使代码看起来更简单。但这不会是打字稿本身。谢谢。现在,我想我会选择只强迫消费者理解和使用“keyof typeof”结构。我很高兴有这个页面作为参考! (3认同)
  • 具体问题是“如何编写一个类型函数,该函数接受枚举的名称并输出其键的并集,而不要求用户在枚举名称之前编写“typeof””。答案是“你不能;枚举作为类型的名称描述了它的值而不是它的键”。您的回答似乎甚至没有试图解决这个问题。`keyof typeof MyEnum` 是您想要的类型,但无法将其转换为类似 `type KeyofTypeof&lt;E&gt; = keyof typeof E` 的类型,因为 `E` 必须是一个类型,而 `typeof` 需要一个值,而不是一个类型。 (2认同)

Pas*_*aye 26

有一个解决方案不需要创建新的泛型类型。

如果你声明一个枚举

enum Season { Spring, Summer, Autumn, Winter };
Run Code Online (Sandbox Code Playgroud)

要获取类型,您只需要使用关键字keyof typeof

let seasonKey: keyof typeof Season;
Run Code Online (Sandbox Code Playgroud)

然后变量按预期工作

seasonKey = "Autumn"; // is fine
// seasonKey = "AA" <= won't compile
Run Code Online (Sandbox Code Playgroud)


Akx*_*kxe 13

这实际上是可能的。

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!
};
Run Code Online (Sandbox Code Playgroud)

游乐场链接


编辑:解除限制!

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!
};
Run Code Online (Sandbox Code Playgroud)

游乐场链接


  • 这工作const enum太!


小智 5

您可以只传递 atype而不是 a value,编译器不会抱怨。typeof正如您所指出的,您可以实现这一目标。
会稍微不那么自动化:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;
Run Code Online (Sandbox Code Playgroud)

您可以将其用作:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;
Run Code Online (Sandbox Code Playgroud)