|
1 |
| -# TypeScript Const Enums |
| 1 | +# Avoid TypeScript Enums |
2 | 2 |
|
3 |
| -We don't use Typescript enums because they are not fully type-safe and can cause surprises. Instead |
4 |
| -of using enums, we use constant objects. |
| 3 | +TypeScript enums are not fully type-safe and can [cause surprises][enum-surprises]. Your code should |
| 4 | +use [constant objects][constant-object-pattern] instead of introducing a new enum. |
5 | 5 |
|
6 | 6 | ## Our Recommended Approach
|
7 | 7 |
|
| 8 | +- Use the same name for your type- and value-declaration. |
| 9 | +- Use `type` to derive type information from the const object. |
| 10 | +- Create utilities to convert and identify enums modelled as primitives. |
| 11 | + |
| 12 | +:::tip |
| 13 | + |
| 14 | +This pattern should simplify the usage of your new objects, improve type safety in files that have |
| 15 | +adopted TS-strict, and make transitioning an enum to a const object much easier. |
| 16 | + |
| 17 | +::: |
| 18 | + |
| 19 | +### Example |
| 20 | + |
| 21 | +Given the following enum: |
| 22 | + |
8 | 23 | ```ts
|
9 |
| -export const PasskeyActions = { |
10 |
| - Register: "register", |
11 |
| - Authenticate: "authenticate", |
| 24 | +export enum CipherType = { |
| 25 | + Login: 1, |
| 26 | + SecureNote: 2, |
| 27 | + Card: 3, |
| 28 | + Identity: 4, |
| 29 | + SshKey: 5, |
| 30 | +}; |
| 31 | +``` |
| 32 | + |
| 33 | +You can redefine it as an object like so: |
| 34 | + |
| 35 | +```ts |
| 36 | +const _CipherType = { |
| 37 | + Login: 1, |
| 38 | + SecureNote: 2, |
| 39 | + Card: 3, |
| 40 | + Identity: 4, |
| 41 | + SshKey: 5, |
12 | 42 | } as const;
|
13 | 43 |
|
14 |
| -export type PasskeyActionValue = (typeof PasskeyActions)[keyof typeof PasskeyActions]; |
| 44 | +type _CipherType = typeof _CipherType; |
| 45 | + |
| 46 | +export type CipherType = _CipherType[keyof _CipherType]; |
| 47 | +export const CipherType: Readonly<{ [K in keyof typeof _CipherType]: CipherType }> = |
| 48 | + Object.freeze(_CipherType); |
| 49 | +``` |
| 50 | + |
| 51 | +And use it like so: |
| 52 | + |
| 53 | +```ts |
| 54 | +// Can be imported together |
| 55 | +import { CipherType } from "./cipher-type"; |
15 | 56 |
|
16 |
| -declare function usePasskeyAction(action: PasskeyActionValue): void; |
| 57 | +// Used as a type |
| 58 | +function doSomething(type: CipherType) {} |
17 | 59 |
|
18 |
| -usePasskey(PasskeyActions.Register); // ✅ Valid |
19 |
| -usePasskey(0); // ❌ Invalid |
| 60 | +// And used as a value (just like a regular `enum`) |
| 61 | +doSomething(CipherType.Card); |
20 | 62 | ```
|
21 | 63 |
|
22 |
| -## Resources Used |
| 64 | +The following utilities may assist introspection: |
| 65 | + |
| 66 | +```ts |
| 67 | +import { CipherType } from "./cipher-type"; |
| 68 | + |
| 69 | +const namesByCipherType = new Map<CipherType, keyof CipherType>( |
| 70 | + Array.fromEntries(Object.entries(CipherType), ([k, v]) => [v, k]), |
| 71 | +); |
| 72 | + |
| 73 | +export function isCipherType(value: number): value is CipherType { |
| 74 | + return namesByCipherType.has(value); |
| 75 | +} |
| 76 | + |
| 77 | +export function asCipherType(value: number): CipherType | undefined { |
| 78 | + return isCipherType(value) ? value : undefined; |
| 79 | +} |
| 80 | + |
| 81 | +export function nameOfCipherType(value: CipherType): keyof CipherType | undefined { |
| 82 | + return namesByCipherType.get(value); |
| 83 | +} |
| 84 | +``` |
23 | 85 |
|
24 |
| -- https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh |
25 |
| -- https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums |
| 86 | +[enum-surprises]: https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh |
| 87 | +[constant-object-pattern]: https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums |
0 commit comments