Use Typescript const assertion instead of enums

Since Typescript 3.4 we can now use const assertion to mark objects as readonly. But the good news of this feature is that it will help us replace the unoptimized enum type by a simple object notation and still avoid an accidental change of the value.

The problem of TS enums

Typescript's enums are very handy because there's nothing like enum yet in Javascript, probably because a JS object is very close from an enum. But when we write TS code, the enum inform about readonly state, which is something that can't be done easily from the object.

However, at build time, the TS enum has to be transpiled to an object and also handle reverse accesses (even if not always used). The result will be a very verbose transpiled output for something that could be simply an object structure. The developer has then to choose between a lighter output by using an object or a better typing comfort by using the enum type...

Here's a concrete example of enum transpilation in TS
(TSConfig is target = es2017)

export enum EHttpStatusCode {
    Ok = 200,
    BadRequest = 400,
    Unauthorized = 401,
    Forbidden = 403,
    NotFound = 404,
    ServerError = 500,
}

will transpile to the following JS

export var EHttpStatusCode;
(function (EHttpStatusCode) {
    EHttpStatusCode[EHttpStatusCode["Ok"] = 200] = "Ok";
    EHttpStatusCode[EHttpStatusCode["BadRequest"] = 400] = "BadRequest";
    EHttpStatusCode[EHttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
    EHttpStatusCode[EHttpStatusCode["Forbidden"] = 403] = "Forbidden";
    EHttpStatusCode[EHttpStatusCode["NotFound"] = 404] = "NotFound";
    EHttpStatusCode[EHttpStatusCode["ServerError"] = 500] = "ServerError";
})(EHttpStatusCode || (EHttpStatusCode = {}));

Pretty verbose isn't it?

The const assertion feature solution

The const assertion feature will help us keep a lighter bundle, but at the same time it will give us the same typing feature than the enum.

This is how to use it:

  1. declare the object like you would in plain JS
  2. add as const after the declaration
export const httpStatusCode = {
    Ok: 200,
    BadRequest: 400,
    Unauthorized: 401,
    Forbidden: 403,
    NotFound: 404,
    ServerError: 500,
} as const;

What this declaration will do, is create the object, but also inform TS that all the properties and the object are readonly.
Talking about the output bundle, the Javascript transpiled result will be exactly the same but without the as const statement for sure.

However we still miss the typing feature

So now yes we have the enum declaration, but we still miss the Type which will be used by TS to type check our code.

For TS check, we will need 1 more declaration (here in the example done in 2 steps for clarity)

class HttpResponse {
 code: HttpStatusCode = 200;
// other stuff here
}

This won't work because our object is a value and we can't directly use it as a type. So we will need to extract the type from it:

type HttpStatusCodeKey = keyof typeof httpStatusCode;
export type HttpStatusCode = typeof httpStatusCode[HttpStatusCodeKey];

Now we have the real type which is representing the union of the httpStatusCode object's values. And the good news is that this type is only used for building TS and is then removed from the transpiled output. So the only thing JS will have is the object.

On TS side it will then be very easy to type the code like

class HttpResponse {
 code: HttpStatusCode = httpStatusCode.Ok;
// other stuff here
}

Also notice the casing convention. The object const is camelCased (httpStatusCode) because it's a value. On the other hand, the type extracted from the object is PascalCased (HttpStatusCode). So it follows usual convention and is then easy to differentiate type from value object.

Conclusion

Yes, this solution is a bit more verbose to write than if it was simply a TS enum type. But this 2 additional lines will save you bytes of code in the transpiled output and that's what matters for your users. At the same time, you will still have the same features available (type checking, readonly values, ...) than the one you would have by using real enum. Win-Win!

Feel free to share your thoughts in the comment đź’¬ or to like đź‘Ť the post if it was interesting for you.

25