📖 TypeScript Mastery - Advanced Generics và Type Constraints
70 phút

Advanced Generics và Type Constraints

Generic Constraints

Basic Constraints

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength([1, 2, 3]); // OK
logLength('hello');    // OK
// logLength(42);      // Error - number không có length

Multiple Constraints

function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const result = mergeObjects(
  { name: 'John', age: 30 },
  { city: 'Hanoi', country: 'Vietnam' }
);
// { name: 'John', age: 30, city: 'Hanoi', country: 'Vietnam' }

Keyof và Lookup Types

Keyof Operator

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

type UserKeys = keyof User; // "id" | "name" | "email"

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { id: 1, name: 'John', email: 'john@example.com' };
const name = getProperty(user, 'name'); // string
// getProperty(user, 'age'); // Error - 'age' không tồn tại trong User

Mapped Types

type Optional<T> = {
  [P in keyof T]?: T[P];
};

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type PartialUser = Optional<User>;
// { id?: number; name?: string; email?: string; }

Conditional Types với Generics

Type Inference trong Conditional Types

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser(): { id: number; name: string } {
  return { id: 1, name: 'John' };
}

type UserReturn = ReturnType<typeof getUser>; // { id: number; name: string }

Distributive Conditional Types

type ToArray<T> = T extends any ? T[] : never;

type StringArray = ToArray<string>; // string[]
type NumberArray = ToArray<number>; // number[]
type UnionArray = ToArray<string | number>; // string[] | number[]

Generic Classes

Generic Class Definition

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

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

  getById(id: number): T | undefined {
    return this.items[id];
  }

  getAll(): T[] {
    return [...this.items];
  }

  remove(predicate: (item: T) => boolean): void {
    this.items = this.items.filter(item => !predicate(item));
  }
}

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

const productRepo = new Repository<Product>();
productRepo.add({ id: 1, name: 'Laptop', price: 1000 });

Generic Constraints với Classes

interface Identifiable {
  id: number | string;
}

class IdentifiableRepository<T extends Identifiable> {
  private items: Map<T['id'], T> = new Map();

  add(item: T): void {
    this.items.set(item.id, item);
  }

  get(id: T['id']): T | undefined {
    return this.items.get(id);
  }

  update(id: T['id'], updates: Partial<T>): void {
    const existing = this.items.get(id);
    if (existing) {
      this.items.set(id, { ...existing, ...updates });
    }
  }
}

Advanced Generic Patterns

Factory Pattern với Generics

interface Factory<T> {
  create(): T;
}

class NumberFactory implements Factory<number> {
  create(): number {
    return Math.random();
  }
}

class StringFactory implements Factory<string> {
  create(): string {
    return Math.random().toString(36).substring(2);
  }
}

function createMultiple<T>(factory: Factory<T>, count: number): T[] {
  return Array.from({ length: count }, () => factory.create());
}

const numbers = createMultiple(new NumberFactory(), 5);
const strings = createMultiple(new StringFactory(), 5);

Builder Pattern với Generics

class QueryBuilder<T> {
  private filters: ((item: T) => boolean)[] = [];

  where<P extends keyof T>(
    property: P, 
    predicate: (value: T[P]) => boolean
  ): QueryBuilder<T> {
    this.filters.push(item => predicate(item[property]));
    return this;
  }

  execute(items: T[]): T[] {
    return items.filter(item => this.filters.every(filter => filter(item)));
  }
}

// Usage
interface User {
  id: number;
  name: string;
  age: number;
  active: boolean;
}

const users: User[] = [
  { id: 1, name: 'John', age: 25, active: true },
  { id: 2, name: 'Jane', age: 30, active: false },
];

const result = new QueryBuilder<User>()
  .where('age', age => age > 25)
  .where('active', active => active === true)
  .execute(users);

📝 Bài tập (1)

  1. Tạo generic validation system với type-safe rules

Bài học "Advanced Generics và Type Constraints" - Khóa học "TypeScript Mastery"