2 REACT FRONTEND

Advanced TypeScript: Interfaces & Generics

🧠 Advanced TypeScript: Generics and Utilities

Understanding string, number, and interface allows you to write safe TypeScript. However, Professional TypeScript involves building dynamic, reusable architectures specifically for large codebases.

In this lesson, we cover Advanced Types, Union operations, and the legendary Generics engine.


1️⃣ Union and Intersection Types

Sometimes a variable can legitimately be more than one thing.

Union Types (|)

The "OR" operator.

// The payment ID can come from a database (number) or a Stripe API (string).
let paymentId: string | number;

paymentId = 40192;     // ✅ Valid
paymentId = "ch_1234"; // ✅ Valid
paymentId = true;      // ❌ Error!

Literal Types: You can lock a variable not just to a type, but to specific values.

let userRole: "Admin" | "Moderator" | "User";

userRole = "Admin"; // ✅ 
userRole = "Hacker"; // ❌ Error: Type '"Hacker"' is not assignable...

Intersection Types (&)

The "AND" operator. It forcibly glues two blueprints together into a massive super-blueprint.

interface HasName { name: string; }
interface HasAge { age: number; }

// A Person must mathematically fulfill BOTH contracts simultaneously.
type Person = HasName & HasAge;

const myFriend: Person = {
    name: "Alex",
    age: 28
};

2️⃣ Type Guards and Narrowing

When using a Union type (string | number), TypeScript restricts you from doing anything dangerous.

function processId(id: string | number) {
    // ❌ ERROR! You can't use .toUpperCase() because what if 'id' is a number?
    return id.toUpperCase(); 
}

To fix this, you must explicitly Narrow the type using an if statement. TypeScript is smart enough to read the if logic and lift the restriction inside that block. This is called a Type Guard.

function processId(id: string | number) {
    if (typeof id === "string") {
        // TypeScript KNOWS for a mathematical fact that 'id' is a string here!
        return id.toUpperCase(); // ✅ Perfectly legal
    } else {
        // TypeScript KNOWS it must be a number here!
        return id.toFixed(2);    // ✅ Perfectly legal
    }
}

3️⃣ Generics: Variables for Types

Generics are the hardest concept in TypeScript, but they are absolutely essential.

Imagine building a reusable Box that can temporarily hold anything.

// ❌ The terrible way: using 'any' defeats the entire purpose of TypeScript
class Box {
    contents: any;
    getContents(): any { return this.contents; }
}

What is a Generic? A Generic is a temporary variable specifically for a Type. It is visually represented by angle brackets <T>. Instead of hardcoding string or number, you tell TypeScript: "When the developer uses this Box, they will pass a Type into <T>, and you will lock the Box to that Type."

// The <TypeParameter> is waiting for input when the function is called!
function createBox<T>(item: T): T {
    return item;
}

// Pass 'string' into <T>. Everywhere in the function that says T instantly becomes 'string'.
const stringBox = createBox<string>("Hello");

// Pass 'number' into <T>.
const numberBox = createBox<number>(100);

// Pass a complex interface!
interface Dog { bark: boolean; }
const dogBox = createBox<Dog>({ bark: true });

This is the foundational logic used by React's useState. When you write useState<string>(""), you are passing string into the T Generic of the Hook!


4️⃣ Enums (Enumerated Types)

An Enum allows you to declare a set of named constants. It makes your code highly readable instead of using random "magic numbers" or easily-misspelled strings across 10 files.

// 1. Define the Options
enum OrderStatus {
    PENDING = 1,
    PROCESSING = 2,
    SHIPPED = 3,
    DELIVERED = 4
}

// 2. Use them explicitly 
const myOrder = {
    id: 994,
    status: OrderStatus.SHIPPED // Much safer than writing status: 3
};

if (myOrder.status === OrderStatus.DELIVERED) {
    sendEmail();
}

5️⃣ Global Utility Types

TypeScript gives you dozens of built-in operations to rapidly mutate architectures without writing them from scratch.

Imagine we have our core User interface:

interface User {
    id: string;
    email: string;
    username: string;
    avatarUrl: string;
}

Partial<T>

Makes every single property inside <T> optional. Perfect for "Update Profile" forms where a user might only send 1 or 2 fields.

// You want to update JUST the username. Partial makes id/email/avatar optional.
const updateData: Partial<User> = { username: "NewName" }; 

Pick<T, Keys>

Extracts a handful of specific properties into a new mini-interface.

// We only need the Name and Avatar for the top right corner of the Navbar
type NavbarUser = Pick<User, "username" | "avatarUrl">;

Omit<T, Keys>

The opposite of Pick. Discards specific properties.

// Copy everything, but strip out the confidential email info!
type PublicUser = Omit<User, "email">; 

💡 Summary Checklist for Advanced TS

  • I actively avoid using any. I use Generics <T> to keep reusable logic strongly typed.
  • I understand how to use | (Union) to grant multiple options, and & (Intersection) to merge blueprints.
  • I always use typeof Type Guards to safely narrow Unions.
  • I use Partial, Pick, and Omit to effortlessly derive DTOs (Data Transfer Objects) instead of copying and pasting the main Interface.

Knowledge Check

Complete this quick quiz to verify your understanding and unlock the next module.