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

Literals & Widening

literal type คือ type ที่มี value ได้ค่าเดียวคือ literal ที่ระบุ "GET" เป็น type และ value เดียวที่ assign ให้ type นี้ได้คือ string "GET" เช่นเดียวกับ 42, true และอื่น ๆ

let method: "GET" = "GET";
method = "POST"; // ❌ Type '"POST"' is not assignable to type '"GET"'.

ลำพัง literal type แทบไม่มีประโยชน์ พลังของตัวเองมาจาก union ของ literal ซึ่ง model ความหมาย “หนึ่งในค่าที่ระบุพวกนี้”:

type Method = "GET" | "POST" | "PUT" | "DELETE";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;

นี่คือวิธีแทน parameter แบบ string หลวม ๆ ด้วยตัวที่แม่นยำ อธิบายตัวเองได้ และ autocomplete ได้

TypeScript จะ widen literal ไปเป็น type ทั่วไปในที่ที่ value อาจเปลี่ยนได้ กฎที่คุณเจอใน Foundations: const เก็บ literal, let widen

const a = "GET"; // type: "GET"
let b = "GET"; // type: string (widen — b reassign ได้)

widening แบบเดียวกันเกิดกับ property ของ object ด้วย เพราะ property ของ object แก้ค่าได้โดย default:

const config = { method: "GET" };
// type: { method: string } — ไม่ใช่ { method: "GET" }
function send(m: "GET" | "POST") {}
send(config.method); // ❌ string assign ให้ "GET" | "POST" ไม่ได้

ถึง config จะเป็น const แต่ property method ยัง reassign ได้ (config.method = "POST" ทำได้) compiler เลย widen เป็น string จุดนี้ทำทุกคนงงครั้งหนึ่งเสมอ

const assertion (as const) บอก compiler ว่า “มองค่านี้เป็น immutable แบบลึก และเก็บทุก literal ให้แคบไว้”

const config = { method: "GET" } as const;
// type: { readonly method: "GET" }
send(config.method); // ✅ ตอนนี้ "GET" อยู่รอด

as const ทำ 3 อย่างพร้อมกัน:

  1. literal ยังเป็น literal"GET" แทน string
  2. property กลายเป็น readonly — สะท้อน immutability ที่คุณสัญญาไว้
  3. array กลายเป็น readonly tuple[1, 2] กลายเป็น readonly [1, 2] ไม่ใช่ number[]
flowchart TB
  val["value: { method: 'GET' }"] -->|default| wide["type: { method: string }
(widened, mutable)"]
  val -->|as const| narrow["type: { readonly method: 'GET' }
(literal, readonly)"]
widening เทียบ as const

as const เปล่งประกายกับ lookup table และ config ที่คุณอยากได้ key/value เป๊ะ ๆ ใน type:

const ROUTES = {
home: "/",
profile: "/profile",
settings: "/settings",
} as const;
type Route = typeof ROUTES[keyof typeof ROUTES];
// type: "/" | "/profile" | "/settings"

ถ้าไม่มี as const ROUTES จะเป็น { home: string; profile: string; settings: string } แล้วคุณก็เสีย path เฉพาะทั้งหมด แต่พอมี คุณ derive union type ที่แม่นยำจากตัวข้อมูลได้ตรง ๆ — pattern ที่คุณจะเจอตลอดในโมดูล type-level

type ของ `const x = "GET"` เทียบกับ `let y = "GET"` คืออะไร?
ทำไม `method` ถึงเป็น string ใน `const config = { method: "GET" }`?
`as const` ทำอะไร?
ทำไม union ของ literal อย่าง "GET" | "POST" ถึงดีกว่า string สำหรับ parameter?