typescript - 在打字稿中获取枚举键作为联合字符串的通用类型?
问题描述
考虑以下打字稿枚举:
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' 仅指一种类型,但在此处用作值。”
这是出乎意料和悲伤的。通过从泛型声明的右侧删除 typeof 并将其添加到特定类型声明中的类型参数中,我可以通过以下方式完全解决它:
type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer. Ick.
不过我不喜欢这种解决方法,因为这意味着消费者必须记住在泛型上对 typeof 进行这种令人讨厌的指定。
是否有任何语法可以让我按照我最初想要的方式指定泛型类型,以便对消费者友好?
解决方案
不,消费者需要使用typeof MyEnum
来引用键为A
、B
和的对象C
。
前面有很长的解释,其中一些你可能已经知道了
您可能知道,TypeScript 为 JavaScript 添加了一个静态类型系统,并且在代码被转译时该类型系统会被删除。TypeScript 的语法是这样的,一些表达式和语句引用运行时存在的值,而其他表达式和语句引用仅在设计/编译时存在的类型。值具有类型,但它们本身不是类型。重要的是,在代码中的某些地方,编译器会期望一个值并将它找到的表达式解释为一个值,如果可能的话,编译器会期望一个类型并将它找到的表达式解释为一个类型。
如果一个表达式可能被解释为一个值和一个类型,编译器不会在意或感到困惑。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}
该行将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() {}
以下是命名类型的一些介绍:
interface Type1 {}
type Type2 = string;
但是有一些声明同时创建了命名值和命名类型,并且像Foo
上面一样,命名值的类型不是命名类型。大的是class
和enum
:
class Class { public prop = 0; }
enum Enum { A, B }
这里,type Class
是 的实例的类型Class
,而value 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
是枚举元素的类型;每个元素的类型的联合。而value Enum
是一个对象,其键是A
and 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
产生 type的类型做任何事情"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;
这迫使您typeof
再次使用并且比您的解决方案更冗长,特别是因为您不能这样做:
type KeysOfEnum = typeof enumKeysAsString(Enum); // error
布莱。对不起。
回顾:
- 这是不可能的;
- 类型和价值 blah blah;
- 仍然不可能;
- 对不起。
希望这有点道理。祝你好运。
推荐阅读
- android - 儿童行号 android
- javascript - 特定客户的用户门户
- windows-10 - 我可以自签名我的非生产内核驱动程序以在我的特定系统上运行吗?
- javascript - 与 jsx 的连接不起作用
- sql-server - 可以在 Ad-Hoc 服务器上运行 SSIS 包以将结果插入另一台服务器吗?
- php - 在 Vue 中使用 LoginController.php
- python - Python 3:如何获得一个随机的 4 位数字?
- c# - 以路径为内容的 WPF 按钮
- acumatica - 获取 Acumatica 附加的公共文件而不进行身份验证
- javascript - NodeJS“readline”模块不输出提示