คุณจะเชี่ยวชาญกลไกการทำ type narrowing และเขียน predicate ของตัวเองเพื่อทำให้ union น่าเชื่อถือยิ่งขึ้น
เปิดบทเรียนนี้ใน KodokonNarrowing คือกลไกที่ TypeScript ใช้ปรับชนิดที่กว้าง (union หรือ unknown) ให้แม่นยำขึ้นด้วยการวิเคราะห์ control flow คุณใช้มันอยู่แล้วโดยไม่ทันคิด: ทุก if ที่ทำกับค่าที่มีชนิดจะกระตุ้นการวิเคราะห์ คอมไพเลอร์จะติดตาม guard ในตัวสามตระกูล: typeof สำหรับ primitive, instanceof สำหรับ instance ของคลาส และ in สำหรับการมีอยู่ของ property การเลือกใช้ให้ถูกต้องช่วยให้คุณไม่ต้อง cast แบบมั่ว ๆ ด้วย as ซึ่งเป็นการปิดการตรวจสอบแทนที่จะชี้นำมัน
function formatValue(
value: string | number | Date
): string {
if (typeof value === "string") {
return value.trim();
}
if (value instanceof Date) {
return value.toISOString();
}
return value.toFixed(2);
}ตัวดำเนินการ in มีค่ามหาศาลเมื่อคุณจัดการอ็อบเจกต์ที่มีโครงสร้างต่างกันและไม่มีฟิลด์ตัวแบ่งแยก แต่ต้องระวัง: in ตรวจการมีอยู่ของคีย์ รวมถึงคีย์ที่สืบทอดมา ไม่ใช่ชนิดของมัน สำหรับข้อมูลภายนอก (API, JSON) มันจะ narrow ตามการประกาศของคุณ ไม่ใช่ตามสิ่งที่มีอยู่จริงตอน runtime กับดักคลาสสิกในโลกจริง: การตรวจ typeof value === "object" จะปล่อยให้ null ผ่านไปได้ เพราะ typeof null เป็น "object" ใน JavaScript ควรเพิ่มการตรวจ null อย่างชัดเจนเสมอ
interface Cat { meow(): void }
interface Dog { bark(): void }
function speak(animal: Cat | Dog): void {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}เมื่อตรรกะของ guard กลับมาใช้ซ้ำได้หรือซับซ้อนเกินกว่าการวิเคราะห์ flow จะรับมือได้ ให้เขียน custom type guard: ฟังก์ชันที่ชนิดค่าที่คืนถูก annotate ว่า value is T จากนั้นคอมไพเลอร์จะเชื่อในสิ่งที่คุณ implement มันเป็นสัญญา: ถ้าการตรวจของคุณไม่ครบถ้วน คุณก็นำ error ตอน runtime ที่ระบบชนิดจะไม่มีวันเห็นกลับเข้ามาอีกครั้ง ตั้งแต่ TypeScript 5.5 เป็นต้นมา predicate แบบง่าย ๆ จะถูก infer อัตโนมัติใน callback อย่าง filter แต่การ annotate อย่างชัดเจนยังเป็นแนวปฏิบัติหลักสำหรับฟังก์ชันที่ export ออกไป
interface User { id: string; email: string }
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "string" &&
"email" in value &&
typeof value.email === "string"
);
}
const raw: unknown =
JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
console.log(raw.email.toLowerCase());
}typeof value === "object" จึงไม่เพียงพอที่จะ narrow ให้เป็นอ็อบเจกต์ที่ไม่ใช่ null?booleanvalue as Uservalue is Userasserts value