TypeScript - Literal Types: A Beginner's Guide

Hello, future coding superstar! Today, we're diving into the exciting world of TypeScript Literal Types. Don't worry if you're new to programming – I'll be your friendly guide through this adventure. By the end of this tutorial, you'll be wielding literal types like a pro!

TypeScript - Literal Types

What are Literal Types?

Before we jump in, let's imagine you're ordering a pizza. You can't just say, "I want a pizza." You need to specify the size: small, medium, or large. In TypeScript, literal types are like those specific pizza sizes – they're exact values that a variable can have.

Syntax

The syntax for literal types is straightforward. You simply use the exact value you want to allow. Let's look at a basic example:

let pizzaSize: "small" | "medium" | "large";

In this case, pizzaSize can only be "small", "medium", or "large". Nothing else is allowed – not even "extra large" or "tiny"!

String Literal Types

String literal types are the most common and easiest to understand. They're exactly what they sound like – specific strings that a variable can be.

Example 1: Days of the Week

type DayOfWeek = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";

let today: DayOfWeek = "Monday";
today = "Friday"; // This is fine
today = "Someday"; // Error! Type '"Someday"' is not assignable to type 'DayOfWeek'.

In this example, DayOfWeek is a type that can only be one of the seven days of the week. Try to assign any other string, and TypeScript will wag its finger at you!

Example 2: Traffic Light Colors

type TrafficLightColor = "Red" | "Yellow" | "Green";

function changeLight(color: TrafficLightColor) {
    console.log(`The light has changed to ${color}`);
}

changeLight("Green"); // Works fine
changeLight("Purple"); // Error! Argument of type '"Purple"' is not assignable to parameter of type 'TrafficLightColor'.

Here, we're ensuring that our traffic light can only change to valid colors. No disco lights allowed on this street!

Numeric Literal Types

Just like with strings, we can create literal types with numbers. This is great for when you want to restrict a value to specific numbers.

Example 3: Dice Rolls

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(): DiceRoll {
    return Math.floor(Math.random() * 6) + 1 as DiceRoll;
}

let myRoll = rollDice();
console.log(`You rolled a ${myRoll}`);

In this example, we're making sure that our dice can only roll numbers from 1 to 6. No loaded dice in our game!

Example 4: Shirt Sizes

type ShirtSize = 36 | 38 | 40 | 42 | 44;

let myShirtSize: ShirtSize = 40;
myShirtSize = 41; // Error! Type '41' is not assignable to type 'ShirtSize'.

Here, we're restricting shirt sizes to specific numbers. No in-between sizes allowed in this store!

Combined Literal Types

The real power of literal types shines when we combine different types of literals.

Example 5: Game Character Status

type CharacterStatus = "Alive" | "Dead" | 1 | 0;

let hero: CharacterStatus = "Alive";
hero = 1; // This is also valid
hero = "Wounded"; // Error! Type '"Wounded"' is not assignable to type 'CharacterStatus'.

In this game, a character can be "Alive", "Dead", 1 (representing alive), or 0 (representing dead). It's like having both a text and a code representation!

Use Cases for Literal Types

Literal types are incredibly useful in many scenarios. Let's explore a few:

1. Function Parameters

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function sendRequest(url: string, method: HttpMethod) {
    // Send the request
    console.log(`Sending ${method} request to ${url}`);
}

sendRequest("https://api.example.com", "GET"); // Works fine
sendRequest("https://api.example.com", "FETCH"); // Error! Argument of type '"FETCH"' is not assignable to parameter of type 'HttpMethod'.

By using a literal type for the HTTP method, we ensure that only valid methods can be used.

2. Configuration Objects

type Theme = "light" | "dark" | "system";
type Language = "en" | "es" | "fr" | "de";

interface AppConfig {
    theme: Theme;
    language: Language;
    notifications: boolean;
}

const myConfig: AppConfig = {
    theme: "dark",
    language: "en",
    notifications: true
};

Literal types help us create strict configuration objects, preventing typos and invalid settings.

3. State Machines

type LoginState = "loggedOut" | "loggingIn" | "loggedIn" | "error";

class LoginManager {
    private state: LoginState = "loggedOut";

    login() {
        this.state = "loggingIn";
        // Perform login logic
        this.state = Math.random() > 0.5 ? "loggedIn" : "error";
    }

    logout() {
        this.state = "loggedOut";
    }

    getState(): LoginState {
        return this.state;
    }
}

Here, we use literal types to represent different states in a login process, ensuring that the state can only be one of the predefined values.

Methods Table

Here's a table summarizing the key methods and concepts we've covered:

Method/Concept Description Example
Type Declaration Defining a new type using literal values type DayOfWeek = "Monday" \| "Tuesday" \| ...
Variable Assignment Assigning a literal type to a variable let today: DayOfWeek = "Monday";
Function Parameters Using literal types in function parameters function sendRequest(method: HttpMethod) {...}
Combined Literals Mixing different types of literals type Status = "Active" \| "Inactive" \| 0 \| 1;
Interface Properties Using literal types in interface properties interface Config { theme: "light" \| "dark" }

And there you have it, my coding apprentice! You've just leveled up in your TypeScript journey by mastering literal types. Remember, like choosing the perfect pizza topping, literal types help you be specific and avoid mistakes. Keep practicing, and soon you'll be creating type-safe code that's as delicious as a perfectly crafted pizza! ??

Credits: Image by storyset