Introduction
TypeScript was designed as a superset of JavaScript, so TS types should not affect JS’s runtime. However there’s couple ways to define constant value.
Object as const:
export const BUTTON_SIZES = {
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
} as const;
as const
will be removed in the compilation time, so only an object will exists in the run time. Adding as const will change type definition from:
{
SMALL: string,
MEDIUM: string,
LARGE: string,
}
into:
{
readonly SMALL: 'small',
readonly MEDIUM: 'medium',
readonly LARGE: 'large',
}
Looks pretty simple however it wouldn’t be so easy to create a type definition of BUTTON_SIZES
value.
type ButonSizeKey = keyof typeof BUTTON_SIZES; // "SMALL" | "MEDIUM" | "LARGE"
type ButonSizeValue = typeof BUTTON_SIZES[ButonSizeKey] // "small" | "medium" | "large"
Enum:
export enum BUTTON_SIZES_ENUM {
SMALL = 'small',
MEDIUM = 'medium',
LARGE = 'large',
};
Looks similar to the previous case however this code will be complied into:
export var BUTTON_SIZES_ENUM;
(function (BUTTON_SIZES_ENUM) {
BUTTON_SIZES_ENUM["SMALL"] = "small";
BUTTON_SIZES_ENUM["MEDIUM"] = "medium";
BUTTON_SIZES_ENUM["LARGE"] = "large";
})(BUTTON_SIZES_ENUM || (BUTTON_SIZES_ENUM = {}));
It’s the same object as in previous case but it takes more space in the final bundle.
Big advantage is the fact that you don’t have to create a separate type for a ButtonSizeValue because enum BUTTON_SIZE
can be use as a type of a value.
But what about case when enum has numbers instead of strings:
export enum PLACE {
FIRST = 1,
SECOND = 2,
};
Will be complied to:
export var PLACE;
(function (PLACE) {
PLACE[PLACE["FIRST"] = 1] = "FIRST";
PLACE[PLACE["SECOND"] = 2] = "SECOND";
})(PLACE || (PLACE = {}));
And this code it the same that:
{1: "FIRST", 2: "SECOND", FIRST: 1, SECOND: 2}
So value become a key and key becomes a value…
Const enum:
export const enum BUTTON_SIZES_CONST_ENUM {
SMALL = 'small',
MEDIUM = 'medium',
LARGE = 'large',
};
Almost the same code as in the previous example will be removed in compilation time.
Usage of enum like this:
const buttonSizeUsage = BUTTON_SIZES_CONST_ENUM.MEDIUM;
Will be complied into:
const buttonSizeUsage = "medium" /* MEDIUM */;
Looks much better than previous example however there’s couple od disadvantages:
const obj = { buttonSize: BUTTON_SIZES_CONST_ENUM } // ERROR: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.
Object.values(BUTTON_SIZES_CONST_ENUM); // ERROR 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.
Union:
The simplest way will be:
type BUTTON_SIZES_UNION = "small" | "medium" | "large";
Type definition will be removed in compilation time and complier will prevent us to pass unknown or different strings.
let fn = (buttonSize: BUTTON_SIZES_UNION) => {};
fn('small');
fn('smal'); // Argument of type '"smal"' is not assignable to parameter of type 'BUTTON_SIZES_UNION'
let str: string;
fn(str); // Argument of type 'string' is not assignable to parameter of type 'BUTTON_SIZES_UNION'.
Is fast in the implementation but has the same disadvantages as const enum
. Also changing value (e.g. “medium” into “regular”) requires modification of each usage, not just one like in enum
/const enum
.
Summary:
Is hard to choose the best way, all of them have advantages and disadvantages. In my opinion const enum
can be good first choice.
Top comments (0)