enums-and-literal-types
Enums and Literal Types in TypeScript:
Enums and Literal Types in TypeScript:
What are Enums:
Enums are a way to group related values under one collective name. They give a name to each member in the group, making your code easier to read and maintain.
enum Direction {
Up,
Down,
Left,
Right
}
Basic Enums:
Basic enums assign numbers to names, starting at zero by default. This makes it easy to represent a group of related numeric values with friendly names.
enum Response {
No = 0,
Yes = 1,
}
String Enums:
String enums map names to strings. This can make your code more informative when you read it or log out these values.
enum Directions {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
Heterogeneous Enums:
Heterogeneous enums mix strings and numbers. They can be confusing and are not commonly used, but they're there if you need them.
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
Enum Members:
Enum members are the individual constants within an enum. You can access these by their name through the enum.
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write
// computed member
G = "123".length
}
Computed Enums:
Computed enums contain values computed at runtime. They give you flexibility when you don't know the value ahead of time.
enum ComputedEnum {
A = Math.random(),
B = A * 2,
}
Enum Flags:
Numeric enums can be used as flags with bitwise operations. This is a trick to store multiple boolean values in a single number.
enum Permission {
Read = 1,
Write = 2,
Execute = 4,
}
let userPermissions = Permission.Read | Permission.Write; // Combines Read and Write permissions
Reverse Mapping:
Numeric enums in TypeScript automatically include reverse mapping, which allows you to get the enum name from a numeric value.
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
Const Enums:
Const enums are evaluated at compile-time and don't exist at runtime, which can improve performance and reduce memory usage.
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
What are Literal Types:
Literal types restrict a variable to only allow values that match a specified literal. This is great for when you want a variable to be really specific.
let trueLiteral: true = true;
let helloLiteral: "hello" = "hello";
String Literal Types:
String literal types limit variables to certain string values, enhancing type safety and readability.
type Direction = 'North' | 'East' | 'South' | 'West';
function move(direction: Direction) {
// function body
}
move('North'); // Valid
move('N'); // Error: 'N' is not assignable to type 'Direction'.
Numeric Literal Types:
Numeric literal types limit variables to certain numeric values. They are useful in scenarios like function overloading.
type SmallPrimes = 2 | 3 | 5 | 7 | 11;
function isSmallPrime(number: SmallPrimes): boolean {
// function body
return true;
}
isSmallPrime(2); // Valid
isSmallPrime(4); // Error: 4 is not assignable to type 'SmallPrimes'.
Boolean Literal Types:
Boolean literal types can only be
true
orfalse
. This is useful when a function expects only a specific boolean value.
function toggleStrictMode(mode: true | false) {
// function body
}
toggleStrictMode(true); // Valid
toggleStrictMode(false); // Valid
Tuple Types with Literal Elements:
You can combine tuples and literal types to represent a fixed-length array with elements of specific types and values.
type RGB = [255, 0, 0]; // Red color in RGB
let red: RGB = [255, 0, 0]; // Valid
let green: RGB = [0, 255, 0]; // Error: Type '[0, 255, 0]' is not assignable to type 'RGB'.
Discriminated Unions:
Literal types are often used in discriminated unions to distinguish between different shapes of data.
interface Circle {
kind: 'circle';
radius: number;
}
interface Square {
kind: 'square';
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === 'circle') {
return Math.PI * shape.radius ** 2;
} else {
return shape.sideLength ** 2;
}
}
Readonly Literal Types:
Literal types can be marked as
readonly
to prevent modification, ensuring greater immutability.
const literalObject: { readonly prop: 'fixed' } = { prop: 'fixed' };
literalObject.prop = 'change'; // Error: Cannot assign to 'prop' because it is a read-only property.
Type Narrowing with Literals:
Literal types help in type narrowing, allowing the TypeScript compiler to identify the exact type within a conditional block.
type YesNo = 'YES' | 'NO';
function processAnswer(answer: YesNo) {
if (answer === 'YES') {
// TypeScript knows 'answer' is 'YES' in this block
} else {
// TypeScript knows 'answer' is 'NO' in this block
}
}
Literal Type Inference:
TypeScript can infer literal types from context. Using
const
oras const
can help with inference.
let x = 'hello'; // Type is string
const y = 'hello'; // Type is 'hello'
let z = { prop: 'hello' } as const; // Type is { readonly prop: 'hello' }
Enum Member Types:
TypeScript allows you to use enum members as types. This is useful in scenarios where only specific enum members are allowed.
enum HttpStatus {
OK = 200,
BadRequest = 400,
Unauthorized
}
function respond(status: HttpStatus.OK | HttpStatus.BadRequest) {
// function body
}
Enums and Literal Types Together:
You can combine enums and literal types to create variables that can only take on specific enum members as their value.
enum Fruit {
Apple,
Banana,
Cherry
}
type FavoriteFruit = Fruit.Apple | Fruit.Cherry;
let myFruit: FavoriteFruit = Fruit.Apple; // Valid
myFruit = Fruit.Banana; // Error: 'Fruit.Banana' is not assignable to type 'FavoriteFruit'.
Understanding enums and literal types will allow you to write more specific and safer code in TypeScript. Enums help you define named constants, enhancing readability, while literal types let you narrow down variables to specific values, improving type safety.