ข้ามไปยังเนื้อหา

Structural Typing

หลายภาษา (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 — compatible

Point กับ 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 fine

user มีทุกอย่างที่ Named ต้องการ (มี name) จึงเข้าได้ ส่วน age ที่เกินมามองไม่เห็นจากฝั่งที่ถือ object นี้ในฐานะ Named

flowchart LR
  val["value:
name, age, email"] -->|มีทุกอย่างที่
Named ต้องการ| target["target type:
Named มี name"]
  target -->|assign ได้ ✅| ok["ยอมรับ"]
structural compatibility ว่าด้วย shape ที่จำเป็นต้องมี

มีอยู่จุดหนึ่งที่ 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 literal

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

"structural typing" หมายความว่าอะไร?
value ที่มี property เกิน assign ให้ type ที่ต้องการน้อยกว่าได้ไหม?
ทำไม assign `{ width: 10, height: 20 }` ตรง ๆ ให้ `Options { width: number }` ถึง error แต่ assign ผ่านตัวแปรกลับไม่ error?
เทคนิคใดให้ความต่างแบบ nominal ใน system ที่เป็น structural?