Keywords: TypeScript | Enums | Key Types
Abstract: This article explores how to use enums as restricted key types for objects in TypeScript. By comparing the compilation behavior, type safety, and mutability control between the `in Enum` and `keyof typeof Enum` approaches, it highlights the advantages of using enum values as keys. Through code examples, the article covers numeric, string, and heterogeneous enums, offering practical recommendations to avoid common pitfalls and achieve stricter type constraints.
In TypeScript, object key types are typically restricted to string or number, but enums enable more precise key constraints. Based on community Q&A data, this article delves into using enums as restricted key types, focusing on the differences between in Enum and keyof typeof Enum.
Basic Methods for Using Enums as Key Types
Since TypeScript's 2018 update, the in Enum syntax allows direct use of enum values as object key types. For example:
enum MyEnum {
First,
Second
}
let obj: { [key in MyEnum]: any } = { [MyEnum.First]: 1, [MyEnum.Second]: 2 };
If not all enum values are required, optional properties can be used:
let obj: { [key in MyEnum]?: any } = { [MyEnum.First]: 1 };
Core Differences Between in Enum and keyof typeof Enum
in Enum compiles to enum values, while keyof typeof Enum compiles to enum keys. This leads to significant differences in type safety and behavior.
Type Safety Comparison
With in Enum, key types are strictly limited to enum values:
enum MyEnum { First, Second }
let obj: { [key in MyEnum]?: any } = { [MyEnum.First]: 1 };
obj[0] = 1; // Allowed, as 0 is the value of MyEnum.First
obj[42] = 1; // Error: Type '42' cannot be used to index type '{ 0?: any; 1?: any; }'
The compiled type is { 0?: any; 1?: any; }, permitting only enum values 0 and 1.
In contrast, keyof typeof Enum may allow non-enum values in numeric enums:
let obj: { [key in keyof typeof MyEnum]?: any } = { First: 1 };
obj[2] = 1; // May be allowed due to an implicit [x: number] index signature
The compiled type is { [x: number]: any; readonly First?: any; readonly Second?: any; }, introducing an additional numeric index signature.
Mutability Control
in Enum allows object property modifications by default, with immutability optionally added via readonly:
let obj: { readonly [key in MyEnum]?: any } = { [MyEnum.First]: 1 };
obj[MyEnum.First] = 2; // Error: read-only property
keyof typeof Enum generates read-only properties by default, requiring -readonly to remove restrictions:
let obj: { -readonly [key in keyof typeof MyEnum]?: any } = { First: 1 };
obj.First = 1; // Allowed
Extended Applications with Enum Types
The in Enum method works with various enum types:
enum StringEnum {
First = "YES",
Second = "NO"
}
let obj: { [key in StringEnum]?: any } = { [StringEnum.First]: 1 };
obj["YES"] = 0; // Allowed
Heterogeneous enums are also supported:
enum HeterogeneousEnum {
First = 1,
Second = "TEXT"
}
let obj: { [key in HeterogeneousEnum]?: any } = { [HeterogeneousEnum.First]: 1 };
obj[1] = 0; // Allowed
obj["TEXT"] = 0; // Allowed
Practical Recommendations and Summary
Based on the analysis, in Enum is recommended because it:
- Provides strict type safety, allowing only enum values as keys
- Compiles in a way that aligns with intuitive enum semantics
- Offers flexible mutability control
- Works with all enum types (numeric, string, heterogeneous)
keyof typeof Enum may introduce unexpected numeric index signatures in some scenarios, increasing type uncertainty. By understanding these differences, developers can leverage TypeScript's type system more effectively to build robust and maintainable code.