考虑以下打字稿枚举:
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。
是否有任何语法允许我按照我最初的要求指定泛型类型,以便对消费者友好?
答案 0 :(得分:25)
不,消费者需要使用typeof MyEnum
来引用密钥为A
,B
和C
的对象。
长远的解释,您可能已经知道的一些
您可能知道,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;
一样,的类型命名值不是命名类型。最重要的是Foo
和class
:
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;
是对象,其键为Enum
和Enum
,其属性是枚举的元素。 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。遗憾。
回顾:
希望这有点道理。祝你好运。
答案 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!
};
编辑:取消限制!
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
一起使用!答案 2 :(得分:0)
您可以只传递 type
而不是 value
,编译器不会抱怨。正如您所指出的,您可以使用 typeof
实现这一点。
将不那么自动:
type AnyEnumKeysAsStrings<TEnumType> = keyof TEnumType;
您可以用作:
type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<typeof MyEnum>;