+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 12 of 355

๐Ÿท ๏ธ Type Aliases in TypeScript: Creating Custom Types Like a Pro

Master TypeScript type aliases to create reusable, readable types, union types, intersection types, and more ๐Ÿš€

๐ŸŒฑBeginner
20 min read

Prerequisites

  • Basic TypeScript types knowledge ๐Ÿ“
  • Understanding of interfaces โšก
  • Functions basics ๐Ÿ’ป

What you'll learn

  • Create and use type aliases effectively ๐ŸŽฏ
  • Master union and intersection types ๐Ÿ—๏ธ
  • Build complex types with utility types ๐Ÿ”
  • Apply type aliases in real projects โœจ

๐ŸŽฏ Introduction

Welcome to the world of TypeScript type aliases! ๐ŸŽ‰ If interfaces are like blueprints for objects, type aliases are like custom labels you can stick on any type - from simple primitives to complex structures.

Think of type aliases as nicknames for types ๐Ÿท๏ธ. Just like you might call your friend โ€œAlexโ€ instead of โ€œAlexander Christopher Thompson IIIโ€, type aliases let you give friendly names to complex types. They make your code more readable, maintainable, and DRY (Donโ€™t Repeat Yourself)!

By the end of this tutorial, youโ€™ll be creating elegant type aliases that make your code a joy to read and work with! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Type Aliases

๐Ÿค” What Are Type Aliases?

Type aliases create new names for existing types. Theyโ€™re like creating a shortcut or bookmark ๐Ÿ”– that points to any type - primitives, objects, unions, functions, or even other aliases!

Unlike interfaces (which only describe object shapes), type aliases can represent:

  • โœจ Primitive types (string, number, boolean)
  • ๐Ÿš€ Union types (this OR that)
  • ๐Ÿ›ก๏ธ Intersection types (this AND that)
  • ๐Ÿ“– Function types
  • ๐ŸŽจ Literal types
  • ๐Ÿ”ง Any complex type combination!

๐Ÿ’ก Type Alias vs Interface

When should you use each?

// ๐ŸŽฏ Type alias - flexible, works with any type
type UserID = string | number;
type Point = { x: number; y: number };
type Callback = (data: string) => void;

// ๐Ÿ”— Interface - best for object shapes
interface User {
  id: UserID; // Can use type alias!
  name: string;
  location: Point;
}

// ๐Ÿ’ก Key differences:
// 1. Type aliases can represent primitives and unions
type Status = "active" | "inactive"; // Can't do with interface!

// 2. Interfaces can be extended/implemented
class Employee implements User {
  id = "EMP001";
  name = "Alice";
  location = { x: 10, y: 20 };
}

// 3. Type aliases can't be merged (interfaces can)
interface Window {
  customProperty: string; // Merges with global Window
}

๐Ÿ”ง Basic Type Aliases

๐Ÿ“ Creating Simple Aliases

Letโ€™s start with the basics:

// ๐ŸŽจ Primitive type aliases
type Username = string;
type Age = number;
type IsActive = boolean;

// โœ… Using type aliases
let currentUser: Username = "TypeScriptNinja";
let userAge: Age = 25;
let isOnline: IsActive = true;

// ๐Ÿ†” ID type that could be string or number
type ID = string | number;

function getUser(id: ID): void {
  console.log(`Fetching user with ID: ${id}`);
}

getUser("USER-123"); // โœ… String ID
getUser(456);        // โœ… Numeric ID

// ๐Ÿ“Š Object type aliases
type Coordinates = {
  x: number;
  y: number;
  z?: number; // Optional for 2D/3D
};

const position2D: Coordinates = { x: 10, y: 20 };
const position3D: Coordinates = { x: 10, y: 20, z: 30 };

// ๐ŸŽฎ Function type aliases
type ClickHandler = (event: MouseEvent) => void;
type AsyncCallback<T> = (data: T) => Promise<void>;

const handleClick: ClickHandler = (event) => {
  console.log(`Clicked at ${event.clientX}, ${event.clientY}`);
};

๐ŸŽจ Literal Type Aliases

Create types from exact values:

// ๐ŸŽฏ String literal types
type Direction = "north" | "south" | "east" | "west";
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

// ๐Ÿ”ข Numeric literal types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
type PowerOfTwo = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128;

// ๐ŸŒˆ Mixed literal types
type GameInput = "UP" | "DOWN" | "LEFT" | "RIGHT" | 32 | 13; // Arrow keys or space/enter

// ๐ŸŽฎ Using literal types
function move(direction: Direction): void {
  switch (direction) {
    case "north":
      console.log("Moving up! โฌ†๏ธ");
      break;
    case "south":
      console.log("Moving down! โฌ‡๏ธ");
      break;
    case "east":
      console.log("Moving right! โžก๏ธ");
      break;
    case "west":
      console.log("Moving left! โฌ…๏ธ");
      break;
  }
}

// ๐ŸŽฒ Dice game example
function rollDice(): DiceRoll {
  return Math.floor(Math.random() * 6 + 1) as DiceRoll;
}

const roll = rollDice();
console.log(`You rolled a ${roll}! ๐ŸŽฒ`);

๐Ÿ’ก Union Types

๐Ÿ”€ Creating Union Types

Union types represent values that can be one of several types:

// ๐ŸŽฏ Basic union types
type StringOrNumber = string | number;
type NullableString = string | null;
type BooleanOrUndefined = boolean | undefined;

// ๐Ÿƒ Real-world example: API responses
type APIResponse<T> = 
  | { success: true; data: T }
  | { success: false; error: string };

// Using the API response type
function handleResponse(response: APIResponse<User>): void {
  if (response.success) {
    console.log(`Welcome, ${response.data.name}! โœ…`);
  } else {
    console.log(`Error: ${response.error} โŒ`);
  }
}

// ๐ŸŽจ Theme system with unions
type ColorScheme = "light" | "dark" | "auto";
type ThemeColor = "primary" | "secondary" | "accent" | "error" | "warning" | "success";

type Theme = {
  scheme: ColorScheme;
  colors: Record<ThemeColor, string>;
};

// ๐ŸŽฎ Game state unions
type GameState = 
  | { status: "menu" }
  | { status: "playing"; level: number; score: number }
  | { status: "paused"; level: number; score: number }
  | { status: "gameOver"; finalScore: number; highScore: number };

function renderGame(state: GameState): void {
  switch (state.status) {
    case "menu":
      console.log("๐ŸŽฎ Main Menu");
      break;
    case "playing":
      console.log(`๐Ÿƒ Level ${state.level} - Score: ${state.score}`);
      break;
    case "paused":
      console.log(`โธ๏ธ Paused at Level ${state.level}`);
      break;
    case "gameOver":
      console.log(`๐Ÿ’€ Game Over! Score: ${state.finalScore}`);
      if (state.finalScore > state.highScore) {
        console.log("๐Ÿ† New High Score!");
      }
      break;
  }
}

๐Ÿ›ก๏ธ Discriminated Unions

Use a common property to distinguish between union members:

// ๐Ÿš— Vehicle type system
type Car = {
  type: "car";
  brand: string;
  doors: number;
  fuelType: "gas" | "electric" | "hybrid";
};

type Motorcycle = {
  type: "motorcycle";
  brand: string;
  engineSize: number; // in CC
};

type Bicycle = {
  type: "bicycle";
  brand: string;
  gears: number;
  isElectric: boolean;
};

type Vehicle = Car | Motorcycle | Bicycle;

// ๐ŸŽฏ Type-safe vehicle handling
function describeVehicle(vehicle: Vehicle): string {
  switch (vehicle.type) {
    case "car":
      return `๐Ÿš— ${vehicle.brand} car with ${vehicle.doors} doors (${vehicle.fuelType})`;
    case "motorcycle":
      return `๐Ÿ๏ธ ${vehicle.brand} motorcycle with ${vehicle.engineSize}cc engine`;
    case "bicycle":
      return `๐Ÿšฒ ${vehicle.brand} bicycle with ${vehicle.gears} gears${vehicle.isElectric ? " (electric)" : ""}`;
  }
}

// ๐Ÿ“ฑ Notification system
type NotificationBase = {
  id: string;
  timestamp: Date;
  read: boolean;
};

type MessageNotification = NotificationBase & {
  type: "message";
  from: string;
  preview: string;
};

type FriendRequestNotification = NotificationBase & {
  type: "friend_request";
  from: string;
  mutualFriends: number;
};

type SystemNotification = NotificationBase & {
  type: "system";
  title: string;
  message: string;
  severity: "info" | "warning" | "error";
};

type Notification = MessageNotification | FriendRequestNotification | SystemNotification;

function renderNotification(notification: Notification): void {
  const baseInfo = `[${notification.timestamp.toLocaleTimeString()}]`;
  
  switch (notification.type) {
    case "message":
      console.log(`๐Ÿ’ฌ ${baseInfo} New message from ${notification.from}: "${notification.preview}"`);
      break;
    case "friend_request":
      console.log(`๐Ÿ‘ฅ ${baseInfo} Friend request from ${notification.from} (${notification.mutualFriends} mutual friends)`);
      break;
    case "system":
      const icon = notification.severity === "error" ? "๐Ÿšจ" : notification.severity === "warning" ? "โš ๏ธ" : "โ„น๏ธ";
      console.log(`${icon} ${baseInfo} ${notification.title}: ${notification.message}`);
      break;
  }
}

๐Ÿš€ Intersection Types

๐Ÿ”— Combining Types

Intersection types combine multiple types into one:

// ๐ŸŽฏ Basic intersection
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // Has both name AND age

const person: Person = {
  name: "Sarah",
  age: 28
};

// ๐Ÿข Employee system with intersections
type Employee = {
  employeeId: string;
  department: string;
  salary: number;
};

type Manager = {
  teamSize: number;
  budget: number;
};

type Developer = {
  programmingLanguages: string[];
  githubUsername: string;
};

// Combining roles
type TechLead = Employee & Developer & Manager;

const techLead: TechLead = {
  // From Employee
  employeeId: "EMP001",
  department: "Engineering",
  salary: 120000,
  // From Developer
  programmingLanguages: ["TypeScript", "Python", "Go"],
  githubUsername: "awesome-dev",
  // From Manager
  teamSize: 5,
  budget: 500000
};

// ๐ŸŽจ Mixin pattern with intersections
type Timestamped = {
  createdAt: Date;
  updatedAt: Date;
};

type Identifiable = {
  id: string;
};

type Versionable = {
  version: number;
};

// Create rich domain objects
type Document = Identifiable & Timestamped & Versionable & {
  title: string;
  content: string;
  author: string;
};

type Comment = Identifiable & Timestamped & {
  text: string;
  authorId: string;
  documentId: string;
};

// ๐Ÿ›ก๏ธ Permission system
type ReadPermission = { canRead: true };
type WritePermission = { canWrite: true };
type DeletePermission = { canDelete: true };
type AdminPermission = { isAdmin: true };

type ReadOnlyUser = ReadPermission;
type StandardUser = ReadPermission & WritePermission;
type ModeratorUser = ReadPermission & WritePermission & DeletePermission;
type SuperUser = ReadPermission & WritePermission & DeletePermission & AdminPermission;

function checkPermissions(user: StandardUser): void {
  if (user.canRead) console.log("โœ… Can read");
  if (user.canWrite) console.log("โœ… Can write");
  // if (user.canDelete) console.log("Can delete"); // Error! StandardUser doesn't have canDelete
}

๐ŸŽจ Advanced Type Aliases

๐Ÿงฎ Generic Type Aliases

Make your types reusable with generics:

// ๐ŸŽฏ Generic wrapper types
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type AsyncData<T> = Promise<T>;

// Usage
type NullableUser = Nullable<User>; // User | null
type OptionalString = Optional<string>; // string | undefined

// ๐Ÿ“ฆ Result type for error handling
type Result<T, E = Error> = 
  | { ok: true; value: T }
  | { ok: false; error: E };

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { ok: false, error: "Division by zero!" };
  }
  return { ok: true, value: a / b };
}

const result = divide(10, 2);
if (result.ok) {
  console.log(`Result: ${result.value}`); // 5
} else {
  console.log(`Error: ${result.error}`);
}

// ๐Ÿ”„ State management types
type LoadingState<T> = 
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error };

// ๐ŸŽฎ Game inventory system
type InventorySlot<T> = {
  item: T | null;
  quantity: number;
  maxStack: number;
};

type Weapon = { type: "weapon"; damage: number; durability: number };
type Armor = { type: "armor"; defense: number; durability: number };
type Consumable = { type: "consumable"; effect: string; uses: number };

type GameItem = Weapon | Armor | Consumable;
type Inventory = InventorySlot<GameItem>[];

// ๐ŸŒณ Tree structure
type TreeNode<T> = {
  value: T;
  children: TreeNode<T>[];
};

const fileSystem: TreeNode<string> = {
  value: "root",
  children: [
    {
      value: "src",
      children: [
        { value: "index.ts", children: [] },
        { value: "types.ts", children: [] }
      ]
    },
    {
      value: "tests",
      children: [
        { value: "index.test.ts", children: [] }
      ]
    }
  ]
};

๐Ÿ› ๏ธ Utility Type Aliases

Create powerful utility types:

// ๐ŸŽฏ Object key extraction
type ObjectKeys<T> = keyof T;
type ObjectValues<T> = T[keyof T];

interface Product {
  id: string;
  name: string;
  price: number;
}

type ProductKeys = ObjectKeys<Product>; // "id" | "name" | "price"
type ProductValues = ObjectValues<Product>; // string | number

// ๐Ÿ”ง Deep readonly type
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

type Config = {
  api: {
    url: string;
    key: string;
  };
  features: {
    darkMode: boolean;
    analytics: boolean;
  };
};

type ImmutableConfig = DeepReadonly<Config>;
// Now config.api.url is readonly too!

// ๐ŸŽจ Conditional types
type IsArray<T> = T extends any[] ? true : false;
type ElementType<T> = T extends (infer E)[] ? E : never;

type Test1 = IsArray<string[]>; // true
type Test2 = IsArray<string>; // false
type Test3 = ElementType<number[]>; // number
type Test4 = ElementType<string>; // never

// ๐Ÿ—๏ธ Builder pattern types
type Builder<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => Builder<T>;
} & {
  build(): T;
};

// ๐Ÿ“ Form field types
type FormField<T> = {
  value: T;
  error?: string;
  touched: boolean;
  dirty: boolean;
};

type FormValues<T> = {
  [K in keyof T]: FormField<T[K]>;
};

interface LoginForm {
  username: string;
  password: string;
  rememberMe: boolean;
}

type LoginFormState = FormValues<LoginForm>;
// Creates form state with FormField for each property!

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Type Alias Recursion

// โŒ Direct recursion causes error
// type RecursiveArray<T> = T | RecursiveArray<T>[];
// Error: Type alias 'RecursiveArray' circularly references itself

// โœ… Solution: Use interface for recursion
interface RecursiveArray<T> {
  value: T | RecursiveArray<T>[];
}

// Or use intermediate type
type NestedArray<T> = T | { items: NestedArray<T>[] };

๐Ÿคฏ Pitfall 2: Union Type Exhaustiveness

type Status = "pending" | "approved" | "rejected";

// โŒ Forgetting to handle all cases
function handleStatus(status: Status) {
  switch (status) {
    case "pending":
      return "Waiting...";
    case "approved":
      return "Success!";
    // Oops! Forgot "rejected"
  }
}

// โœ… Exhaustive check
function handleStatusSafely(status: Status): string {
  switch (status) {
    case "pending":
      return "Waiting... โณ";
    case "approved":
      return "Success! โœ…";
    case "rejected":
      return "Rejected โŒ";
    default:
      // This ensures we handle all cases
      const _exhaustive: never = status;
      return _exhaustive;
  }
}

๐Ÿ˜ต Pitfall 3: Type Alias vs Interface Confusion

// โŒ Using type alias when interface is better
type UserType = {
  name: string;
  age: number;
};

// Can't extend with 'extends' keyword
// type AdminType extends UserType { } // Error!

// โœ… Use interface for object types that need extension
interface User {
  name: string;
  age: number;
}

interface Admin extends User {
  permissions: string[];
}

// โœ… Use type alias for unions, primitives, etc.
type ID = string | number;
type Status = "active" | "inactive";

๐Ÿ› ๏ธ Best Practices

๐ŸŽฏ Type Alias Best Practices

  1. ๐Ÿ“ Descriptive Names: Make intent clear

    // โŒ Vague
    type Data = string | number;
    
    // โœ… Descriptive
    type UserIdentifier = string | number;
  2. ๐Ÿ—๏ธ Single Responsibility: One concept per type

    // โŒ Too many concerns
    type UserDataAndStatus = {
      name: string;
      email: string;
      isActive: boolean;
      lastLogin: Date;
    };
    
    // โœ… Separate concerns
    type UserData = { name: string; email: string };
    type UserStatus = { isActive: boolean; lastLogin: Date };
    type User = UserData & UserStatus;
  3. ๐ŸŽจ Use Union Types for States: Model mutually exclusive states

    // โœ… Clear state representation
    type RequestState<T> = 
      | { type: "idle" }
      | { type: "loading" }
      | { type: "success"; data: T }
      | { type: "error"; message: string };
  4. โœจ Prefer Type Aliases for: Unions, intersections, and utilities

    type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
    type Coordinates = [x: number, y: number];
    type AsyncCallback<T> = (data: T) => Promise<void>;
  5. ๐Ÿ”ง Create Utility Types: Build reusable type utilities

    type Nullable<T> = T | null;
    type Keys<T> = keyof T;
    type Values<T> = T[keyof T];
    type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Type-Safe Event System

Create a flexible event system using type aliases:

๐Ÿ“‹ Requirements:

  • โœ… Define various event types (click, input, custom)
  • ๐Ÿท๏ธ Create type-safe event handlers
  • ๐Ÿ‘ฅ Support event listeners with proper typing
  • ๐Ÿ“… Add event metadata (timestamp, source)
  • ๐ŸŽจ Handle different payload types

๐Ÿš€ Bonus Points:

  • Add event filtering by type
  • Create event history tracking
  • Implement type-safe event emitter

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Event type definitions
type EventMetadata = {
  timestamp: Date;
  source: string;
  userId?: string;
};

// Base event structure
type BaseEvent<T extends string, P = void> = EventMetadata & {
  type: T;
  payload: P;
};

// ๐ŸŽฎ Specific event types
type ClickEvent = BaseEvent<"click", {
  x: number;
  y: number;
  target: string;
}>;

type InputEvent = BaseEvent<"input", {
  field: string;
  value: string;
  previousValue: string;
}>;

type NavigationEvent = BaseEvent<"navigation", {
  from: string;
  to: string;
  method: "push" | "replace" | "back";
}>;

type UserEvent = BaseEvent<"user", {
  action: "login" | "logout" | "register";
  email: string;
}>;

type CustomEvent<T extends string, P> = BaseEvent<T, P>;

// ๐Ÿ”— Union of all events
type AppEvent = ClickEvent | InputEvent | NavigationEvent | UserEvent;

// ๐ŸŽฏ Event handler types
type EventHandler<T extends AppEvent> = (event: T) => void | Promise<void>;
type UnsubscribeFn = () => void;

// ๐Ÿ“Š Event listener map
type EventListenerMap = {
  [K in AppEvent["type"]]?: EventHandler<Extract<AppEvent, { type: K }>>[];
};

// ๐Ÿ—๏ธ Event emitter class
class TypedEventEmitter {
  private listeners: EventListenerMap = {};
  private eventHistory: AppEvent[] = [];
  
  // ๐Ÿ“ Subscribe to events
  on<T extends AppEvent["type"]>(
    eventType: T,
    handler: EventHandler<Extract<AppEvent, { type: T }>>
  ): UnsubscribeFn {
    if (!this.listeners[eventType]) {
      this.listeners[eventType] = [];
    }
    
    this.listeners[eventType]!.push(handler as any);
    
    // Return unsubscribe function
    return () => {
      const handlers = this.listeners[eventType];
      if (handlers) {
        const index = handlers.indexOf(handler as any);
        if (index > -1) {
          handlers.splice(index, 1);
        }
      }
    };
  }
  
  // ๐Ÿš€ Emit events
  emit<T extends AppEvent>(event: T): void {
    // Add to history
    this.eventHistory.push(event);
    
    // Get handlers for this event type
    const handlers = this.listeners[event.type];
    if (handlers) {
      handlers.forEach(handler => {
        try {
          (handler as EventHandler<T>)(event);
        } catch (error) {
          console.error(`Error in ${event.type} handler:`, error);
        }
      });
    }
  }
  
  // ๐Ÿ” Get filtered history
  getHistory<T extends AppEvent["type"]>(
    eventType?: T
  ): T extends undefined ? AppEvent[] : Extract<AppEvent, { type: T }>[] {
    if (!eventType) {
      return this.eventHistory as any;
    }
    
    return this.eventHistory.filter(
      event => event.type === eventType
    ) as any;
  }
  
  // ๐Ÿ“Š Get event statistics
  getStats(): Record<AppEvent["type"], number> {
    const stats: Partial<Record<AppEvent["type"], number>> = {};
    
    this.eventHistory.forEach(event => {
      stats[event.type] = (stats[event.type] || 0) + 1;
    });
    
    return stats as Record<AppEvent["type"], number>;
  }
}

// ๐ŸŽฎ Usage example
const eventEmitter = new TypedEventEmitter();

// Subscribe to click events
const unsubscribeClick = eventEmitter.on("click", (event) => {
  console.log(`๐Ÿ–ฑ๏ธ Clicked at (${event.payload.x}, ${event.payload.y}) on ${event.payload.target}`);
});

// Subscribe to navigation events
eventEmitter.on("navigation", (event) => {
  console.log(`๐Ÿงญ Navigating from ${event.payload.from} to ${event.payload.to}`);
});

// Subscribe to user events
eventEmitter.on("user", async (event) => {
  console.log(`๐Ÿ‘ค User ${event.payload.action}: ${event.payload.email}`);
  
  if (event.payload.action === "login") {
    // Emit navigation after login
    eventEmitter.emit({
      type: "navigation",
      payload: { from: "/login", to: "/dashboard", method: "replace" },
      timestamp: new Date(),
      source: "auth"
    });
  }
});

// ๐Ÿš€ Emit events
eventEmitter.emit({
  type: "click",
  payload: { x: 100, y: 200, target: "button#submit" },
  timestamp: new Date(),
  source: "ui"
});

eventEmitter.emit({
  type: "user",
  payload: { action: "login", email: "[email protected]" },
  timestamp: new Date(),
  source: "auth",
  userId: "USER-123"
});

// ๐Ÿ“Š Check statistics
console.log("Event Statistics:", eventEmitter.getStats());

// ๐Ÿ” Get specific event history
const clickHistory = eventEmitter.getHistory("click");
console.log(`Total clicks: ${clickHistory.length}`);

// ๐Ÿงน Cleanup
unsubscribeClick();

// ๐ŸŽฏ Custom event type
type GameEvent = CustomEvent<"game", {
  action: "start" | "pause" | "end";
  score?: number;
  level?: number;
}>;

// Extend union type
type ExtendedAppEvent = AppEvent | GameEvent;

// Helper type to extract event payload
type EventPayload<T extends AppEvent, K extends AppEvent["type"]> = 
  T extends { type: K } ? T["payload"] : never;

// Usage
type ClickPayload = EventPayload<AppEvent, "click">; // { x: number; y: number; target: string; }

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered TypeScript type aliases! Hereโ€™s what you can now do:

  • โœ… Create type aliases for any type ๐Ÿ’ช
  • โœ… Build union types for flexible values ๐ŸŽฏ
  • โœ… Combine types with intersections ๐Ÿ—๏ธ
  • โœ… Use generic aliases for reusable types ๐Ÿ›ก๏ธ
  • โœ… Design better APIs with clear type names! ๐Ÿš€

Remember: Type aliases make your code more readable and maintainable - use them to express your intent clearly! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve become a type alias expert!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Complete the event system exercise
  2. ๐Ÿ—๏ธ Refactor existing code to use type aliases
  3. ๐Ÿ“š Learn about conditional types
  4. ๐ŸŒŸ Explore template literal types!

Remember: Great types lead to great code. Keep creating those aliases! ๐Ÿš€

Happy coding! ๐ŸŽ‰๐Ÿš€โœจ