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);