在Typescript中使用枚举作为限制键类型

217
enum类型可以用作key类型吗?目前似乎只有这种声明,其中key可以是numberstring类型。是否可以创建类似于以下示例的内容:
enum MyEnum
{
    First,
    Second
}

var layer:{[key:MyEnum]:any};
6个回答

371

自2018年起,在Typescript中有一种更简便的方式,无需使用keyof typeof

let obj: { [key in MyEnum]: any} =
 { [MyEnum.First]: 1, [MyEnum.Second]: 2 };

不需要包含所有的键:

let obj: { [key in MyEnum]?: any} =
 { [MyEnum.First]: 1 };

要了解 inkeyof typeof 之间的区别,请继续阅读。

in Enum vs keyof typeof Enum

in Enum编译为枚举,而keyof typeof编译为枚举

使用keyof typeof,你无法更改枚举属性:

let obj: { [key in keyof typeof MyEnum]?: any} = { First: 1 };
obj.First = 1;
// Cannot assign to 'First' because it is a read-only property.

除非你使用-readonly,否则...
let obj: { -readonly [key in keyof typeof MyEnum]?: any} = { First: 1 };
obj.First = 1; // works

但是你可以使用任何整数键吗?
let obj: { [key in keyof typeof MyEnum]?: any} = { First: 1 };
obj[2] = 1;

keyof typeof将编译为:

{
    [x: number]: any;
    readonly First?: any;
    readonly Second?: any;
}

请注意[x: number]readonly属性。这个[x: number]属性在string enum中不存在。
但是使用in Enum,你可以改变对象:
enum MyEnum {
    First,  // default value of this is 0
    Second, // default value of this is 1
}

let obj: { [key in  MyEnum]?: any} = { [MyEnum.First]: 1 };
obj[MyEnum.First] = 1; // can use the enum...
obj[0] = 1;            // but can also use the enum value, 
                       // as it is a numeric enum by default

这是一个数字枚举。但我们不能使用任何数字。
obj[42] = 1;
// Element implicitly has an 'any' type because 
// expression of type '42' can't be used to index type '{ 0?: any; 1?: any; }'.
// Property '42' does not exist on type '{ 0?: any; 1?: any; }'.

声明编译为:
{
    0?: any;
    1?: any;
}

我们只允许使用枚举的值01
这符合我们对枚举的预期,没有像keyof typeof那样的意外情况。
它适用于stringheterogenous类型的枚举。
enum MyEnum
{
    First = 1,
    Second = "YES"
}

let obj: { [key in  MyEnum]?: any} = { [MyEnum.First]: 1, [MyEnum.Second]: 2 };
obj[1] = 0;
obj["YES"] = 0;

这里的类型是:

{
    1?: any;
    YES?: any;
}

使用readonly获得不可变性:

let obj: { readonly [key in MyEnum]?: any} = { 
    [MyEnum.First]: 1,
};
obj[MyEnum.First] = 2;
// Cannot assign to '1' because it is a read-only property.

... 这使得这些键 只读

{
    readonly 1?: any;
    readonly 2?: any;
}

摘要

in Enum keyof typeof Enum
编译为枚举 编译为枚举
不允许超出枚举范围的值 如果使用数字枚举,可以允许超出枚举范围的数值
可以更改对象,通过readonly实现不可变性 无法在没有-readonly的情况下更改枚举属性。其他超出枚举范围的数值可以被更改

尽可能使用in Enum。如果您的代码库使用keyof typeof Enum,请注意这些陷阱。


5
看起来效果不错。使用'typeof'时,似乎我的对象中的一个键是从枚举的键而非该键的值创建的。 - Aleks Grunwald
如果我尝试定义一个具有相同结构的接口,它不起作用。有什么想法吗? - Sachin Gupta
3
“不必包含所有的密钥:” -> 正是我想要的。 - mTrebusak
4
Keyof typeof 不等同于枚举中的键,它创建的类型实际上是由枚举键组成,而不是值。 - Sergey Solomatin
@SachinGupta 我也遇到了同样的问题,似乎用类型替换接口可以解决它。 type Layer = { [key in MyEnum]?: any }; 而不是 interface Layer { [key in MyEnum]?: any } - Dávid Leblay

139
是的。只需输入。
let layer:{[key in keyof typeof MyEnum]: any}
自Typescript 2.1版本起,可以使用keyof关键字。有关详细信息,请参阅TypeScript文档。 仅使用keyof枚举无法正常工作(您将获得enum类型的键而不是枚举常量),因此您必须键入keyof typeof

这似乎正是我正在寻找的。谢谢。 - Hivaga
11
如果你不想强制包含所有枚举值,可以使用{[key in keyof typeof MyEnum]?: any}?表示某些枚举的缺失是可以接受的)。 - Aidin
无法与我一起工作,出现以下错误 计算属性名称必须是“字符串”、“数字”、“符号”或“任何类型” - Mohamed Abu Galala
4
请参考下面Hugo Elhaj-Lahsen的回答。这个回答可能已经过时了! - Sebastian
Hugo Elhaj-Lahsen的下面的答案确实更好。这个会使所有属性都变成只读,这可能不是你想要的。 - Dana
显示剩余3条评论

133

简短回答:

let layer: Partial<Record<MyEnum, unknown>>;

详细回答:

我有同样的问题,我想让下面的代码段正常运行。

enum MyEnum {
    xxx = "xxx",
    yyy = "yyy",
    zzz = "zzz",
}

type myType = ...; // Fill here

const o: myType = { // keys should be in MyEnum, values: number
   [MyEnum.xxx]: 2,
   "hi": 5, // <--- Error: complain on this one, as "hi" is not in enum
}

o[MyEnum.yyy] = 8; // Allow it to be modified later

从这个问题的其他答案开始,我得到了以下内容:

type myType = {[key in keyof typeof MyEnum]: number};

但是它会提示缺少 "yyy" 的 o。所以我需要告诉它这个对象将只拥有枚举键的一部分,而不是全部。因此我需要添加 ?,得到:

type myType = {[key in keyof typeof MyEnum]?: number};

在代码的末尾添加了一行修改对象的代码后,它就出现问题了。现在它抱怨通过keyof继承的类型将其属性设置为readonly,并且我无法在第一次创建后对其进行更改! :| 事实上,在 Typescript Playground中悬停在myType上,会显示:

type myType = {
    readonly xxx?: number | undefined;
    readonly yyy?: number | undefined;
    readonly zzz?: number | undefined;
}

现在,要移除不需要的readonly属性,我发现可以使用:

type myType = {-readonly [key in keyof typeof myEnum1]?: number };

虽然有些丑陋,但是它能够正常工作

直到我尝试了Typescript Utility Types,才找到了我想要的东西!

type myType = Partial<Record<keyof typeof MyEnum, number>>;

:)


看起来不错,但我的问题是你所有的 myType 值必须具有相同的类型(在你的例子中是 number)。我想能够做类似这样的事情:type myType = { readonly xxx?: number; readonly yyy?: string; }你觉得这可能吗? - SudoPlz
@SudoPlz 我建议你为此提出一个新问题。这里的问题是“如何将键限制为枚举类型”。我认为你的问题不同。但是,作为一个猜测,Partial<Record<MyEnum, number|string>Partial<Record<MyEnum,number>> | { yyy?: string} 可能适合你。 - Aidin
不错,但是这个解决方案的问题在于数字可能未定义。 - walox
@walox TypeScript 曾经在区分 undefined 和缺失/可选方面存在问题 -- 请参见 https://github.com/microsoft/TypeScript/issues/13195。最近似乎有所改善(自一个月前起!你可以传递一个标志来捕获它:https://github.com/microsoft/TypeScript/pull/43947) - Aidin

27

对于那些寻求一种获取枚举而不是的方法的人,你只需要将代码从以下更改:

type myType = Partial<Record<MyEnum, number>>;

对此:

type myType = Partial<Record<keyof typeof MyEnum, number>>;

6

实际上,我没有做任何变通就使它运作了。

以下是一些情况:

enum EnumString {
  a="a",
  b="b"
}

enum EnumNum {
  a,
  b
}

type Type = "a" | "b"


let x1:{[k in EnumString]?:any }={a:1} //working
let x2:{[k in EnumNum]?:any }={a:1} //error: Type '{ a: number; }' is not assignable to type '{ 0?: any; 1?: any; }'
let x3:{[k in Type]?:any }={a:1} //working; defining a union type is easer than defining an enum string 

let y1:{[k: EnumString]?:any }={a:1}
let y2:{[k: EnumNum]?:any }={a:1}
let y3:{[k in Type]?:any }={a:1}

let z1:{[k in keyof typeof EnumString]?:any }={a:1}
let z2:{[k in keyof typeof EnumNum]?:any }={a:1}
let z3:{[k: keyof typeof Type]?:any }={a:1}

需要注意的是,即使 "z1" 运行良好,但由于它是只读的,你以后将无法对其进行修改。你可以使用关键字 -readonly 来移除这个限制。

x1.a=2 
x3.a=2
z1.a=2 //error: read only

我创建了一个typescript playground,以便您自行查看不同情况的问题。


1

摘要

enum Person {
  FirstName = 'FirstName',
  LastName = 'LastName',
}

// Solution 1
type PersonType1 = { [key in Person]?: string }
const persona1: PersonType1 = {}
persona1[Person.FirstName] = "Bear"
persona1[Person.LastName] = "Belly"
persona1.FirstName = "Bear"
persona1.LastName = "Belly"

// Solution 2
type PersonType2 = Partial<Record<keyof typeof Person, string>>
const persona2: PersonType2 = {}
persona2[Person.FirstName] = "Bear"
persona2[Person.LastName] = "Belly"
persona2.FirstName = "Bear"
persona2.LastName = "Belly"

// Solution 3
type PersonType3 = { -readonly [key in keyof typeof Person]?: string }
const persona3: PersonType3 = {}
persona3[Person.FirstName] = "Bear"
persona3[Person.LastName] = "Belly"
persona3.FirstName = "Bear"
persona3.LastName = "Belly"

// Solution 4 without "-readonly", it does not allow to change.
type PersonType4 = { [key in keyof typeof Person]?: string }
const persona4: PersonType4 = {}
persona4[Person.FirstName] = "Bear" // Cannot assign to 'FirstName' because it is a read-only property.
persona4[Person.LastName] = "Belly" // Cannot assign to 'LastName' because it is a read-only property.
persona4.FirstName = "Bear" // Cannot assign to 'FirstName' because it is a read-only property.
persona4.LastName = "Belly" // Cannot assign to 'LastName' because it is a read-only property.

PersonType1,PersonType2,PersonType3都编译为
type PersonTypeX = {
    FirstName?: string | undefined;
    LastName?: string | undefined;
} 

PersonType4编译为
type PersonType4 = {
    readonly FirstName?: string | undefined;
    readonly LastName?: string | undefined;
}

因此,您会遇到只读错误。
我个人喜欢解决方案1。足够简短。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接