After trying enums in TS a long time back I also realized it was best to avoid them. Here is my solution to this:
// filename: role.ts
export const Role = {
CUSTOMER: 'customer',
ADMIN: 'admin',
SYSTEM: 'system',
STAFF: 'staff',
} as const;
type TRole = keyof typeof Role;
export type TUserRole = typeof Role[TRole];
Using this structure I can reference any of my roles by `Role.CUSTOMER` and the value is `customer` because it's just a `Record<string, string>` at the end of the day. But I am able to type things by using my `TUserRole` so a function can required that the input be one of the values above. For me this is really clean and easy to use. Note the `type TRole` isn't exported as it's only an intermediary in this process, I could just as well name it `type temp` in all my files and never worry about conflicts.
This way I'm not spreading "special" strings all over my code (always a sign of code smell for me), I can changed everything at a central location, and it's valid JS (it's just an object).
EDIT: I know that `as const` seems unnecessary but I'm pretty sure it's needed for some reason, I whittled this down to the smallest/simplest code block I could and I just copy/paste this pattern whenever I need enum-like functionality.
:facepalm: of course I can, I don't know why it never occurred to me. Probably a case of "if it's ain't broke" but going forward I'll probably switch to this style.
I disagree. Enums are fine if they're string-only. It's only the numeric enums that cause the issues everyone complains about enums for. If there were a lint for making sure all enums are string-only, it would be the best solution, IMO.
AFAIK, your solution is (slightly) worse than an enum in several ways and better in none:
export enum Role {
CUSTOMER = 'customer',
ADMIN = 'admin',
SYSTEM = 'system',
STAFF = 'staff',
}
I think that implements everything yours does, but is easier to grok and fewer lines/declarations.
> Const enums can only use constant enum expressions and unlike regular enums they are completely removed during compilation. Const enum members are inlined at use sites.
I remember reading somewhere that the TypeScript devs considered const enums to be a mistake and recommend against using them. I don't remember why, though.
const enums are one of the few cases where type information changes the emitted JS, something that's arguably a bigger problem than TypeScript-specific, but still just syntax sugar, syntax highlighted in the article.
Const enums are erased at compile time. If you have a reference to `MyEnum.VAR`, TS has to check whether the enum is a const enum, and if so, replace with something like `1 /* VAR */`. This means that the type information in one file (where the enum is defined) is necessary to determine the proper output of any file that uses it.
Thank you for the answer! I won't waste your time because I'm sure the answer is somewhere on the web, but off the top of my head, I don't understand why TS "has" to emit different JS. I would've assumed that the entire point of a const enum is to inline the raw value in the emitted JS, and thus, the programmer should be careful to remember/know that the code is dealing with raw ints/strings.
You potentially get a problem for every '.' expression. In this code:
import {Something} from './file';
console.log(Something.PROP);
TypeScript doesn't know what to emit without type information. If Something is a class, then the JS will look the same. But if it's a const enum, then TypeScript has to erase the Something.PROP expression and replace it with the constant value of that enum member, since Something will not exist at runtime.
This way I'm not spreading "special" strings all over my code (always a sign of code smell for me), I can changed everything at a central location, and it's valid JS (it's just an object).
EDIT: I know that `as const` seems unnecessary but I'm pretty sure it's needed for some reason, I whittled this down to the smallest/simplest code block I could and I just copy/paste this pattern whenever I need enum-like functionality.