Generic Functions & Constraints
type parameter ส่วนใหญ่ถูก infer ไม่ใช่เขียนเอง
หัวข้อที่มีชื่อว่า “type parameter ส่วนใหญ่ถูก infer ไม่ใช่เขียนเอง”ส่วนใหญ่คุณไม่ต้องเขียน type argument เลย — inference เติมให้จาก value ที่คุณส่งเข้าไป:
function first<T>(arr: T[]): T | undefined { return arr[0];}
const a = first([1, 2, 3]);// ^? number | undefined — T inferred as numberconst b = first(["x", "y"]);// ^? string | undefined — T inferred as stringคุณ ส่ง type argument ตรง ๆ ได้ (first<number>([1, 2, 3])) แต่แทบไม่จำเป็น type parameter ไหลมาจากจุดที่เรียก
constraint: extends จำกัดว่า T เป็นอะไรได้บ้าง
หัวข้อที่มีชื่อว่า “constraint: extends จำกัดว่า T เป็นอะไรได้บ้าง”T ที่ไม่มี constraint เป็นอะไรก็ได้ ดังนั้นคุณจะสมมติว่า property อะไรไม่ได้เลย เมื่อ function ต้องการให้ T มี shape บางอย่าง ให้ constrain ด้วย extends:
// ❌ without a constraint, T might not have .lengthfunction longest<T>(a: T, b: T) { return a.length > b.length ? a : b; // Error: Property 'length' does not exist on type 'T'}
// ✅ constrain T to things that have a numeric lengthfunction longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b;}
longest([1, 2], [1, 2, 3]); // ✅ arrays have length → returns number[]longest("ab", "abc"); // ✅ strings have length → returns stringlongest(1, 2); // ❌ number has no lengthอ่าน T extends { length: number } ว่า “T ต้อง assignable ให้ { length: number }” — คือ T เป็น subtype อะไรก็ได้ที่มี length เป็น number อย่างน้อย ที่สำคัญคือ return type ยังเป็น T อยู่ ดังนั้นคุณยังได้ type ที่ เฉพาะเจาะจง (number[], string) ไม่ได้ widen ไปเป็น constraint
constraint แบบ keyof: getter ที่ปลอดภัย
หัวข้อที่มีชื่อว่า “constraint แบบ keyof: getter ที่ปลอดภัย”constraint อ้างถึง type parameter อีกตัวได้ ตัวอย่างคลาสสิกคือ get ที่ type-safe:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}
const user = { name: "Ada", age: 36 };const name = getProp(user, "name");// ^? stringconst age = getProp(user, "age");// ^? numbergetProp(user, "email"); // ❌ "email" is not a key of userK extends keyof T บอกว่า key ต้องเป็นหนึ่งใน key จริง ๆ ของ T และ return type T[K] คือ type ของ property ตัวนั้นเป๊ะ ๆ นี่คือกระดูกสันหลังของ utility เกี่ยวกับ object ที่ type-safe
default type parameter
หัวข้อที่มีชื่อว่า “default type parameter”เหมือน default argument ของ function — type parameter มี default ได้ ซึ่งจะถูกใช้เมื่อ infer ไม่ได้และไม่ได้ส่งมา:
interface Container<T = string> { value: T;}
const c1: Container = { value: "hi" }; // T defaults to stringconst c2: Container<number> = { value: 1 }; // T explicitly numberdefault พบบ่อยกับ generic class และ interface ที่มีค่า fallback ที่สมเหตุสมผล
อย่า generic เกินจำเป็น
หัวข้อที่มีชื่อว่า “อย่า generic เกินจำเป็น”generics เป็นเครื่องมือ ไม่ใช่เป้าหมาย type parameter ที่ปรากฏ แค่ครั้งเดียว ใน signature มักเป็นความผิดพลาด — ไม่ได้เชื่อมอะไรเลย ก็เลยควรเป็น type ตรง ๆ หรือ unknown ไปเลย:
// pointless generic: T is used once, links nothingfunction log<T>(x: T): void { console.log(x); }// just write:function log(x: unknown): void { console.log(x); }generic จะคุ้มค่าก็ต่อเมื่อ type parameter เชื่อมตำแหน่งตั้งแต่สองที่ขึ้นไป (input ไป output, หรือ parameter ตัวหนึ่งไปอีกตัว) ถ้า type parameter ไม่ได้สร้าง ความสัมพันธ์ ก็ตัดทิ้ง