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

时间:2018-05-16 17:36:15

标签: typescript generics enums

考虑以下打字稿枚举:

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.

但是这会产生编译错误:&#34;&#39; TEnum&#39;仅指一种类型,但在此处被用作值。&#34;

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

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

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

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

3 个答案:

答案 0 :(得分:25)

不,消费者需要使用typeof MyEnum来引用密钥为ABC的对象。

长远的解释,您可能已经知道的一些

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

如果表达式可以被解释为值和类型,编译器就不会关心或混淆。例如,对于{的两种风格,它非常高兴。 {1}}在以下代码中:

null

let maybeString: string | null = null; 的第一个实例是一个类型,第二个实例是一个值。

也没问题
null

其中第一个let Foo = {a: 0}; type Foo = {b: string}; 是命名值,第二个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} 将通过JavaScript,它将在运行时使用JavaScript typeof operator并生成一个字符串。但是let TypeofBar = typeof bar;;被删除,并使用TypeScript type query operator检查TypeScript为名为type TypeofBar = typeof bar的值分配的静态类型。

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

bar

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

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

但是有一些声明可以创建两者一个命名值一个命名类型,并且像上面的interface Type1 {} type Type2 = string; 一样,的类型命名值不是命名类型。最重要的是Fooclass

enum

此处,类型 class Class { public prop = 0; } enum Enum { A, B } Class实例的类型,而 { {1}}是构造函数对象。 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; 对象,其键为EnumEnum,其属性是枚举的元素。 A不是B

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} 放入其中,并获取对象 type KeysOfEnum = EnumKeysAsStrings<Enum>; // "A" | "B" 的键。但如您所见,类型Enum与对象Enum 不同。不幸的是,这种类型对价值一无所知。这有点像说:

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 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)]; } 的类型将为 const someKey = enumKeysAsString(Enum); 。是的但是然后将它用作类型你必须查询它:

someKey

会强制您再次使用"A" | "B",并且比您的解决方案更加冗长,特别是因为您无法执行此操作:

 type KeysOfEnum = typeof someKey;

Blegh。遗憾。

回顾:

  • 这不可能;
  • 类型和价值观BLAH BLAH;
  • 仍然不可能;
  • SORRY。

希望这有点道理。祝你好运。

答案 1 :(得分:5)

实际上是可能的。

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


编辑:取消限制!

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


  • 也可以与const enum一起使用!

答案 2 :(得分:0)

您可以只传递 type 而不是 value,编译器不会抱怨。正如您所指出的,您可以使用 typeof 实现这一点。
将不那么自动:

type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;

您可以用作:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;