Structural Typing
shape ไม่ใช่ชื่อ
หัวข้อที่มีชื่อว่า “shape ไม่ใช่ชื่อ”หลายภาษา (Java, C#) ใช้ nominal typing: type สองตัวเข้ากันได้ก็ต่อเมื่อมีชื่อที่ประกาศร่วมกันหรือมีสาย inheritance เชื่อมกัน ส่วน TypeScript ใช้ structural typing: type สองตัวเข้ากันได้ถ้า shape ตรงกัน ไม่ว่าชื่อจะเป็นอะไร
interface Point { x: number; y: number; }interface Coord { x: number; y: number; }
let p: Point = { x: 1, y: 2 };let c: Coord = p; // ✅ different names, identical shape — compatiblePoint กับ Coord ไม่เคยถูกประกาศว่าเกี่ยวข้องกัน แต่ TypeScript ไม่สนใจ value ที่มี x: number และ y: number เข้าได้กับทั้งคู่ บางทีเรียกว่า “duck typing” — ถ้าเดินเหมือน Point และร้องเหมือน Point value ตัวนั้นก็ คือ Point ในสายตาของ type checker
มี property เกินมาก็ไม่เป็นไร — เพราะเป็นความสัมพันธ์แบบ subset
หัวข้อที่มีชื่อว่า “มี property เกินมาก็ไม่เป็นไร — เพราะเป็นความสัมพันธ์แบบ subset”value มี property เกิน มาได้ และยัง assign ให้ type ที่ขอน้อยกว่าได้ ข้อนี้ตามมาจาก model แบบเซตโดยตรง: เซตของ “object ที่มี x, y และ z” เป็น subset ของ “object ที่มี x, y”
interface Named { name: string; }
const user = { name: "Ada", age: 36 };const n: Named = user; // ✅ user has name (plus extra) — that's fineuser มีทุกอย่างที่ Named ต้องการ (มี name) จึงเข้าได้ ส่วน age ที่เกินมามองไม่เห็นจากฝั่งที่ถือ object นี้ในฐานะ Named
flowchart LR val["value: name, age, email"] -->|มีทุกอย่างที่ Named ต้องการ| target["target type: Named มี name"] target -->|assign ได้ ✅| ok["ยอมรับ"]
ข้อยกเว้น: excess property check บน object literal
หัวข้อที่มีชื่อว่า “ข้อยกเว้น: excess property check บน object literal”มีอยู่จุดหนึ่งที่ TypeScript เข้มขึ้น เมื่อคุณ assign object literal ตรง ๆ จะ flag property ที่ target ไม่รู้จัก — เพราะ literal ที่มี key แปลก ๆ เกือบจะเป็น typo หรือความผิดพลาดเสมอ
interface Options { width: number; }
const a: Options = { width: 10, height: 20 };// ❌ Error: 'height' does not exist in type 'Options'.
const raw = { width: 10, height: 20 };const b: Options = raw; // ✅ no error — not a fresh literalassign object ตัวเดียวกัน ผ่านตัวแปรกลับผ่านได้ เพราะตอนนี้ใช้กฎ structural ปกติแล้ว (property เกินไม่เป็นไร) excess property check คือ safety net แคบ ๆ ที่ตั้งใจไว้จับ bug ยอดฮิต “พิมพ์ชื่อ option ผิด” — ไม่ใช่การเปลี่ยน model ที่อยู่เบื้องหลัง
ทำไมเรื่องนี้ถึงสำคัญ
หัวข้อที่มีชื่อว่า “ทำไมเรื่องนี้ถึงสำคัญ”structural typing คือเหตุผลที่ TypeScript ทำงานกับ object ธรรมดาได้ลื่น และเป็นเหตุผลที่คุณแทบไม่ต้องประกาศว่า type หนึ่ง “implements” อีก type และเป็นเหตุผลที่ library สองตัวที่มี Point type ของตัวเองใช้ด้วยกันได้เลย ต้นทุนคือ type ที่ไม่เกี่ยวกันแต่ shape เหมือนกันจะใช้แทนกันได้ — บางครั้งคุณ อยาก ให้ type ต่างกันแบบ nominal (UserId ที่ไม่ใช่แค่ string ตัวไหนก็ได้) ซึ่งคุณปลอมได้ด้วย branded type (เจาะลึกทีหลังในโมดูล Practical Mastery)