Generic Classes & Interfaces
generic interface และ type alias
หัวข้อที่มีชื่อว่า “generic interface และ type alias”interface หรือ type alias รับ type parameter ได้เหมือน function เป๊ะ ตัวคลาสสิกคือ container ที่ type แล้ว:
interface Box<T> { value: T;}
const numBox: Box<number> = { value: 42 };const strBox: Box<string> = { value: "hi" };numBox.value.toFixed(2); // ✅ value is numbertype alias ทำงานเหมือนกัน และมักดีกว่าสำหรับ union และ function:
type Pair<A, B> = { first: A; second: B };type Result<T> = { ok: true; value: T } | { ok: false; error: string };
const r: Result<number> = { ok: true, value: 10 };Result<T> คือ generic discriminated union — หนึ่งใน pattern ที่มีประโยชน์ที่สุดใน TypeScript และเป็นสิ่งที่เราจะกลับมาพูดในบท error-handling
generic class: parameter fix ต่อ instance
หัวข้อที่มีชื่อว่า “generic class: parameter fix ต่อ instance”เมื่อ class เป็น generic type parameter ของตัวเองจะถูกเลือก ตอนที่คุณ construct instance และทุก method ของ instance นั้นใช้ร่วมกัน:
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]; }}
const numbers = new Stack<number>();numbers.push(1);numbers.push("two"); // ❌ Argument of type 'string' is not assignable to 'number'const top = numbers.pop();// ^? number | undefinedพอเขียน new Stack<number>() T ก็เป็น number ตลอดอายุของ object นั้น หลายครั้งคุณไม่ต้องเขียนด้วยซ้ำ — new Stack() แล้ว push(1) ครั้งแรก infer ไม่ได้ แต่หลาย design ให้ inference เติม T จาก argument ของ constructor
type parameter ต่อ method
หัวข้อที่มีชื่อว่า “type parameter ต่อ method”type parameter ที่ประกาศบน method (ไม่ใช่บน class) จะถูกเลือกใหม่ทุกครั้งที่เรียก อิสระจาก parameter ของ class:
class Collection<T> { constructor(private items: T[]) {}
// T is the class parameter (fixed per instance) first(): T | undefined { return this.items[0]; }
// U is a method parameter (fresh per call) map<U>(fn: (item: T) => U): U[] { return this.items.map(fn); }}
const c = new Collection([1, 2, 3]); // T = numberconst lengths = c.map((n) => n.toString().length);// ^? number[] — U inferred as number, per this callconst labels = c.map((n) => `item-${n}`);// ^? string[] — U inferred as string, a different callT ถูกตัดสินครั้งเดียว (ตอน construct) ส่วน U ถูกตัดสินทุกครั้งที่เรียก map การรู้ว่า parameter ตัวไหนเป็นของ class และตัวไหนเป็นของ method คือกุญแจในการอ่าน signature ของ generic class
implement generic interface
หัวข้อที่มีชื่อว่า “implement generic interface”class จะ implement generic interface ได้โดย fix parameter หรือคงความ generic ไว้เอง:
interface Repository<T> { getById(id: string): T | undefined; save(entity: T): void;}
// fix the parameter to a concrete typeclass UserRepository implements Repository<User> { getById(id: string): User | undefined { /* ... */ return undefined; } save(entity: User): void { /* ... */ }}