🏠 Home / Hub

🔷 TypeScript Lesson 05 — Generics

← Back to TypeScript Menu  |  🏠 Hub

1. Why Generics?

// Problem: type-specific functions (code duplication)
function firstString(arr: string[]): string { return arr[0]; }
function firstNumber(arr: number[]): number { return arr[0]; }

// With any — loses type safety
function firstAny(arr: any[]): any { return arr[0]; }
const val = firstAny([1, 2, 3]);
val.toUpperCase();   // no error at compile time — runtime crash!

// With Generic — type-safe AND reusable!
function first<T>(arr: T[]): T {
  return arr[0];
}

const num = first([1, 2, 3]);       // T = number, returns number
const str = first(["a", "b"]);      // T = string, returns string
const usr = first([{id: 1}, {id: 2}]); // T = {id:number}

num.toFixed(2);       // ✅ IDE knows it's number
str.toUpperCase();    // ✅ IDE knows it's string
Generic = "type variable" — call 사 ish TypeScript infers the type at usage site

2. Generic Functions

// Multiple generics
function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}
const p = pair("hello", 42);   // [string, number]

// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const user = { id: 1, name: "Ko", email: "ko@test.com" };
getProperty(user, "name");   // ✅ string
getProperty(user, "id");     // ✅ number
getProperty(user, "phone");  // ❌ Error: 'phone' doesn't exist!

// Generic API wrapper
async function fetchData<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as T;
}

interface User { id: number; name: string; }
interface Post { id: number; title: string; }

const users = await fetchData<User[]>("/api/users");  // User[]
const post  = await fetchData<Post>("/api/posts/1"); // Post

users[0].name;   // ✅ auto-complete!

3. Generic Interfaces & Classes

// Generic interface
interface ApiResponse<T> {
  data:    T;
  status:  number;
  message: string;
  meta?: {
    total: number;
    page:  number;
    limit: number;
  };
}

// Usage
const userResponse: ApiResponse<User[]>  = { data: [], status: 200, message: "OK" };
const postResponse: ApiResponse<Post>   = { data: { id: 1, title: "..." }, status: 200, message: "OK" };

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

  push(item: T): void    { this.items.push(item); }
  pop():  T | undefined  { return this.items.pop(); }
  peek(): T | undefined  { return this.items[this.items.length - 1]; }
  isEmpty(): boolean     { return this.items.length === 0; }
  get size(): number     { return this.items.length; }
}

const numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.pop();   // 2, type: number

const strStack = new Stack<string>();
strStack.push("a");   // only strings allowed

4. Built-in Utility Types

UtilityDescriptionExample
Partial<T>All fields optionalPartial<User>
Required<T>All fields requiredRequired<Partial<User>>
Readonly<T>All fields readonlyReadonly<User>
Pick<T,K>Select fieldsPick<User,'id'|'name'>
Omit<T,K>Exclude fieldsOmit<User,'password'>
Record<K,V>Key-value mapRecord<string,number>
Exclude<T,U>Exclude from unionExclude<'a'|'b'|'c','a'>
ReturnType<F>Function return typeReturnType<typeof fn>
// Practical utility type usage
interface User { id: number; name: string; email: string; password: string; createdAt: Date; }

// Create DTO (no id, no timestamps)
type CreateUserDTO = Omit<User, 'id' | 'createdAt'>;
// Update DTO (all optional, no id)
type UpdateUserDTO = Partial<Omit<User, 'id'>>;
// Safe response (no password)
type UserResponse  = Omit<User, 'password'>;
// Config (all readonly)
type UserConfig    = Readonly<User>;
💡 Utility types = boilerplate ကို eliminate — same interface ကနေ multiple views ဖန်တီးနိုင်

← TS 04  |  Next: TS 06 → TypeScript + Vue →

📌 Study Checklist