考虑下面的打印脚本枚举:
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参数中:
type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer. Ick.
我不喜欢这种变通方法,因为这意味着消费者必须记住在泛型上指定typeof。
是否有任何语法允许我按照最初的要求指定泛型类型,以善待使用者?
5条答案
按热度按时间ffx8fchx1#
不,使用者需要使用
typeof MyEnum
来引用键为A
、B
和C
的对象。正如你可能知道的,TypeScript给JavaScript添加了一个静态类型系统,当代码被翻译时,这个类型系统会被删除。TypeScript的语法是这样的,一些表达式和语句引用运行时存在的 * 值 *,而其他表达式和语句引用只存在于设计/编译时的 * 类型 *。值 * 有 * 类型,但它们本身不是类型。重要的是,代码中存在编译器将期望值并在可能的情况下将其找到的表达式解释为值的一些地方,以及编译器将期望类型并在可能的情况下将其找到的表达式解释为类型的其它地方。
null
有两种风格,编译器对此非常满意:null
的第一个示例是类型,第二个示例是值。其中第一个
Foo
是命名值,第二个Foo
是命名类型。注意,值Foo
的类型是{a: number}
,而类型Foo
是{b: string}
。它们不相同。甚至
typeof
运算符也有双重生命。* 表达式typeof x
总是期望x
是一个值 *,但是typeof x
本身可以是一个值或者类型,这取决于上下文:let TypeofBar = typeof bar;
这一行代码将进入JavaScript,它将在运行时使用JavaScript typeof operator并生成一个字符串。已擦除,并且它正在使用TypeScript类型查询运算符检查TypeScript已分配给名为bar
的值的静态类型。现在,TypeScript中引入名称的大多数语言构造要么创建一个命名值,要么创建一个命名类型。下面是对命名值的一些介绍:
下面是对命名类型的一些介绍:
但是也有一些声明同时创建了一个命名值和**一个命名类型,而且,就像上面的
Foo
一样,命名值的类型不是命名类型,最大的声明是class
和enum
:这里,* type *
Class
是Class
的 * instance * 的类型,而 * value *Class
是 * constructor * 对象,并且typeof Class
不是Class
:并且,* 类型 *
Enum
是枚举的 * 元素 * 的类型;每个元素的类型的并集。而 * value *Enum
是一个 * object *,其键是A
和B
,其属性是枚举的元素。并且typeof Enum
不是Enum
:回到你的问题上,你想发明一个类型运算符,它的工作原理是这样的:
在这里你放入 * type *
Enum
,然后取出 * object *Enum
的键,但是正如你在上面看到的,**类型Enum
和对象Enum
**是不一样的,不幸的是类型不知道任何关于值的信息,这有点像说:很明显,如果你这样写,你会发现你不能对类型
0 | 1
做任何事情,这会产生类型"A" | "B"
,为了使它工作,你需要传递一个知道Map的类型,这个类型是typeof Enum
...这就像
如果
type EnumKeysAsString<T> = keyof T
,这是可能的。所以你只能让消费者指定
typeof Enum
。有什么解决办法吗?嗯,你也许可以使用一些东西来做一个值,比如一个函数?然后你可以打电话
someKey
的类型是"A" | "B"
,是的,但是要用它作为 * type *,你必须查询它:这将迫使您再次使用
typeof
,甚至比您的解决方案更冗长,尤其是您无法执行以下操作:呃。抱歉。
总结一下:
hgncfbus2#
其实是有可能的。
Playground链接
编辑:取消限制!
Playground链接
const enum
!weylhg0b3#
有一种解决方案不需要创建新的泛型类型。
如果声明枚举
要获得该类型,只需使用关键字
keyof
typeof
然后变量按预期工作
pes8fvy94#
你可以只传递一个
type
而不是value
,编译器不会抱怨,正如你所指出的,这是用typeof
实现的。只是不那么自然:
您可以将其用作:
sdnqo3pr5#
如果我正确理解了OP问题,Akxe回答:下面是一个可能的进一步简化。使用typescript类型实用程序。记录〈Keys,Type〉
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
例如: