DEV Community

Syed Mohammed Faham
Syed Mohammed Faham

Posted on

Exploring TypeScript: Types, Interfaces, Generics, and Enums

TypeScript, a powerful superset of JavaScript, has rapidly gained popularity among developers for its ability to bring type safety and advanced features to the dynamic world of JavaScript. In this blog, we'll dive into four key aspects of TypeScript that set it apart: Types, Interfaces, Generics, and Enums. Understanding these features will not only help you write more robust code but also enable you to harness the full potential of TypeScript in your projects.

What is TypeScript?

Before we jump into the specifics, let's briefly recap what TypeScript is. TypeScript is an open-source programming language developed by Microsoft that builds on JavaScript by adding static types. This means that you can define the types of variables, function parameters, return values, and more, allowing TypeScript to catch errors at compile time rather than at runtime. This added layer of type safety makes your code more predictable, easier to refactor, and helps you avoid common bugs.

Types in TypeScript

In general programming terms, types are like labels that tell you what kind of data you're working with. For example, in TypeScript, you can have a variable that holds a number, a string, or a boolean (true/false). When you know what type of data a variable holds, you can avoid many common mistakes.

Basic Types

Here are some basic types in TypeScript:

  • number: For numbers like 5, 10, or 3.14.
  • string: For text, like "hello" or "TypeScript".
  • boolean: For true/false values.

Here's a simple example:

let age: number = 25;
let name: string = "John";
let isStudent: boolean = true;
Enter fullscreen mode Exit fullscreen mode

In this example, age is a number, name is a string, and isStudent is a boolean. If you try to assign a different type to these variables, TypeScript will give you an error.

Custom Types: Type Aliases

Sometimes, you need more complex types that describe specific kinds of data. This is where custom types, also known as type aliases, come in handy. A type alias is a way to give a new name to a type or a combination of types.

For example, imagine you want to create a type for an ID that could be either a number or a string:

type ID = number | string;

let userId: ID = 123; // valid
let productId: ID = "abc123"; // also valid
Enter fullscreen mode Exit fullscreen mode

Here, ID is a custom type that can be either a number or a string. This makes your code more flexible and easier to understand.

Combining Types: Union and Intersection Types

TypeScript also allows you to combine types using union and intersection types:

  • Union Types: A variable can be one of several types. For example, number | string means the variable can be either a number or a string.
  • Intersection Types: A variable must satisfy multiple types. For example, Type A & Type B means the variable must satisfy both Type A and Type B. Here’s an example using union types:
type SuccessResponse = {
    status: "success";
    data: string;
};

type ErrorResponse = {
    status: "error";
    error: string;
};

type APIResponse = SuccessResponse | ErrorResponse;

function handleResponse(response: APIResponse) {
    if (response.status === "success") {
        console.log("Data:", response.data);
    } else {
        console.log("Error:", response.error);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, 'APIResponse' can be either a 'SuccessResponse' or an 'ErrorResponse', and the handleResponse function handles both types correctly.

Interfaces: Describing the Shape of Objects

An interface in TypeScript is like a blueprint for objects. It defines what properties and methods an object should have, along with their types.
Interfaces are used to define the structure of an object. They act as a blueprint, making sure that an object has the required properties with the correct types.

Defining and Using Interfaces

Here’s a basic example of an interface:

interface User {
    id: number;
    name: string;
    email: string;
}

function getUserInfo(user: User): string {
    return `User: ${user.name}, Email: ${user.email}`;
}

const user: User = {
    id: 1,
    name: "John Doe",
    email: "john.doe@example.com"
};

console.log(getUserInfo(user)); // Outputs: User: John Doe, Email: john.doe@example.com
Enter fullscreen mode Exit fullscreen mode

In this example, the 'User' interface defines what a User object should look like: it must have an id (a number), a name (a string), and an email (also a string). The 'getUserInfo' function uses this interface to ensure that the object passed to it has the correct structure.

Extending Interfaces

Interfaces can be extended, meaning you can create new interfaces based on existing ones. This is useful when you want to build on top of an existing structure:

interface Person {
    name: string;
    age: number;
}

interface Employee extends Person {
    employeeId: number;
}

const employee: Employee = {
    name: "Jane Smith",
    age: 30,
    employeeId: 101
};

console.log(`Employee: ${employee.name}, Age: ${employee.age}, ID: ${employee.employeeId}`);
// Outputs: Employee: Jane Smith, Age: 30, ID: 101
Enter fullscreen mode Exit fullscreen mode

In this example, Employee extends the Person interface, so it has all the properties of Person (name and age) plus a new one: employeeId.

Generics: Writing Flexible and Reusable Code

Generics allow you to write flexible and reusable code. They enable you to create components (like functions or classes) that can work with different types while still being type-safe.

Why Use Generics?

Imagine you have a function that takes an array and returns the first element:

function getFirstElement(arr: any[]): any {
    return arr[0];
}
Enter fullscreen mode Exit fullscreen mode

This function works, but it uses any, which means TypeScript won’t check what type of data is being returned. To make this function safer, you can use generics:

function getFirstElement<T>(arr: T[]): T {
    return arr[0];
}
Enter fullscreen mode Exit fullscreen mode

Now, 'T' is a placeholder for any type. The function will return the first element of the same type as the array, whether it’s a string, number, or any other type.

Practical Example: Generic Classes

Generics can also be used in classes. Here’s an example:

class Stack<T> {
    private items: T[] = [];

    push(item: T): void {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // Outputs: 2
Enter fullscreen mode Exit fullscreen mode

In this example, Stack is a generic class that works with any type of data. You can create a stack of numbers, strings, or any other type.

Enums: Defining a Set of Named Constants

Enums (short for "enumerations") allow you to define a set of named constants. They make your code more readable and easier to manage, especially when dealing with a fixed set of related values.

Defining and Using Enums

Here’s a simple example of an enum:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

function move(direction: Direction) {
    switch (direction) {
        case Direction.Up:
            console.log("Moving up");
            break;
        case Direction.Down:
            console.log("Moving down");
            break;
        case Direction.Left:
            console.log("Moving left");
            break;
        case Direction.Right:
            console.log("Moving right");
            break;
    }
}

move(Direction.Up); // Outputs: Moving up
Enter fullscreen mode Exit fullscreen mode

In this example, Direction is an enum with four possible values: Up, Down, Left, and Right. This makes your code easier to understand and reduces the chance of errors.

Numeric and String Enums

TypeScript supports both numeric and string enums. By default, enums are numeric, starting from 0. However, you can also create string enums:

enum Status {
    Active = "ACTIVE",
    Inactive = "INACTIVE",
    Pending = "PENDING"
}

function checkStatus(status: Status) {
    console.log(`The current status is ${status}`);
}

checkStatus(Status.Active); // Outputs: The current status is ACTIVE
Enter fullscreen mode Exit fullscreen mode

Note: String enums are useful when you need more descriptive values.\

Conclusion

TypeScript's advanced features, such as Types, Interfaces, Generics, and Enums, provide a strong foundation for building scalable, maintainable, and robust applications. By leveraging these features, you can write code that is not only type-safe but also more expressive and easier to understand.

Types allow you to define complex and meaningful structures, Interfaces provide a blueprint for object shapes, Generics enable flexible and reusable code, and Enums help you define a set of named constants. Together, these features make TypeScript an invaluable tool for modern web development.

Whether you're just starting with TypeScript or looking to deepen your understanding, mastering these concepts will undoubtedly make you a more proficient and confident developer.

Top comments (0)