Skip to content

Type Narrowing ใน TypeScript อธิบาย

วิธีที่ TypeScript ทำให้ชนิดแคบลงภายใน branch จัดกลุ่มตามเทคนิค: guard ในตัว, predicate ที่เขียนเอง, assertion function, discriminated union, การไหลของความเท่ากันและ truthiness และการแคบลงแบบนามแฝงและแบบเลื่อนเวลา แต่ละแถวจับคู่เทคนิคกับตัวอย่างบรรทัดเดียว

Narrowing คือวิธีที่ TypeScript เปลี่ยนชนิดกว้างให้แคบลงภายใน branch ของโค้ด เพื่อพิสูจน์ว่าการเข้าถึง property ปลอดภัย การตรวจทุกครั้ง (typeof, in, predicate, discriminator) บอกคอมไพเลอร์ว่า ตั้งแต่ตรงนี้ค่ามีความจำเพาะมากขึ้น เทคนิคด้านล่างคือชุดที่ใช้งานจริง

ตารางอ้างอิง · 23 รายการ
23 of 23 rows
Guard ในตัว
จำกัด primitive ให้แคบลง: string, number, boolean และอื่น ๆif (typeof x === 'string') x.toUpperCase()
แคบลงเป็น instance ของคลาสหรือสิ่งที่ construct ได้if (e instanceof Error) e.message
แคบลงโดยตรวจว่ามี property นั้นอยู่บนค่าif ('id' in obj) obj.id
แคบค่าใด ๆ ให้เป็น array; แคบ union ให้เหลือสมาชิกที่เป็น arrayif (Array.isArray(x)) x.map(f)
Guard ที่เขียนเอง
ฟังก์ชัน predicate ที่ค่าตอบกลับทำให้อาร์กิวเมนต์แคบลงfunction isUser(x): x is User { return !!x.id }
predicate แคบลงเป็น union ของหลายชนิดได้ ไม่ใช่ชนิดเดียวเท่านั้นfunction isKey(x): x is string | number
predicate แบบ intersection เก็บชนิดตั้งต้นไว้แล้วเพิ่มฟิลด์เข้าไปfunction hasId(x): x is x & { id: string }
assertion function ที่โยน exception เมื่อ x ไม่ใช่ชนิดนั้นfunction assertUser(x): asserts x is User
Union แบบแยกด้วยแท็ก
แคบ union ด้วยฟิลด์ literal ที่ทุกสมาชิกมีร่วมกัน ผ่าน switch หรือ iftype E = { kind: 'a'; a: number } | { kind: 'b'; b: string }
แต่ละ case แคบลงเหลือสมาชิกหนึ่งตัวของ unionswitch (u.kind) { case 'a': u.a }
แคบ union ที่ไม่มีแท็ก เมื่อ property มีอยู่บนสมาชิกเดียวเท่านั้นif ('b' in u) u.b
แต่ละ case คือเงื่อนไข; case ที่เข้าเงื่อนไขจะแคบค่านั้นลงswitch (true) { case typeof x === 'string': }
default กำหนดค่าให้ never เพื่อพิสูจน์ว่า switch ครอบคลุมทุกกรณีdefault: { const x: never = u }
ความเท่ากัน & การไหลของการทำงาน
ตัด null และ undefined ออกหลังการตรวจความเท่ากันif (x === null) return; x.toFixed()
ตัดค่า falsy ออก (0, '', null, undefined, NaN)if (!x) return; // x is defined here
property ที่ทอดสายแล้วเป็น truthy พิสูจน์ว่าค่าตั้งต้นมีอยู่จริงif (user?.active) user.name
non-null assertion ตัด null และ undefined ออกโดยไม่ตรวจx!.toFixed(2)
definite assignment: ประกาศว่า x ถูกกำหนดค่าก่อนถูกอ่านlet name!: string; init()
การแคบลงมีผลภายใน branch และหลัง return ก่อนเวลาif (!arr) return; arr.map(...)
นามแฝง & การเลื่อนเวลา
const boolean ที่เก็บผลตรวจไว้ จะแคบค่าทุกที่ที่มันถูกทดสอบconst isStr = typeof x === 'string'; if (isStr) …
ตัวแปร const รักษาการแคบลงไว้ภายใน closure ที่จับมันไว้const s = get(); if (s) use(() => s.length)
ตั้งแต่ TS 5.4, let หรือพารามิเตอร์รักษาการแคบลงใน closure หลังการกำหนดค่าครั้งสุดท้ายlet n: string | null = get(); if (n) use(() => n.length)
unknown ใช้งานไม่ได้จนกว่า guard จะแคบมันลงเป็นชนิดจริงif (typeof u === 'string') u.length