Type Guards ใน TypeScript
Type Guards
Section titled “Type Guards”Type Guards คือเทคนิคที่ช่วย narrow (จำกัด) type ลง ณ runtime ทำให้ TypeScript รู้ว่า ณ จุดนั้นข้อมูลเป็น type อะไร
typeof Guard
Section titled “typeof Guard”function padLeft(value: string, padding: string | number): string { // typeof narrowing if (typeof padding === "number") { // TS รู้ว่า padding เป็น number return " ".repeat(padding) + value; } // TS รู้ว่า padding เป็น string return padding + value;}
padLeft("hello", 4); // " hello"padLeft("hello", ">>> "); // ">>> hello"function padLeft(value, padding) { if (typeof padding === "number") { return " ".repeat(padding) + value; } return padding + value; // ทำงานเหมือนกัน แต่ไม่มี compile-time check}def pad_left(value: str, padding: str | int) -> str: if isinstance(padding, int): return " " * padding + value return padding + value-- SQL ใช้ CASE เพื่อ handle ต่าง typeSELECT CASE WHEN typeof(padding) = 'integer' THEN REPEAT(' ', padding) || value ELSE padding || valueEND FROM data;instanceof Guard
Section titled “instanceof Guard”class ApiError { constructor(public statusCode: number, public message: string) {}}
class ValidationError { constructor(public field: string, public errors: string[]) {}}
function handleError(error: ApiError | ValidationError): string { if (error instanceof ApiError) { // TS รู้ว่าเป็น ApiError — เข้าถึง statusCode ได้ return `API Error ${error.statusCode}: ${error.message}`; } // TS รู้ว่าเป็น ValidationError return `Validation Error on ${error.field}: ${error.errors.join(", ")}`;}in Operator Guard
Section titled “in Operator Guard”interface Fish { swim: () => void; name: string; }interface Bird { fly: () => void; name: string; }
function move(animal: Fish | Bird): string { if ("swim" in animal) { animal.swim(); // TS รู้ว่าเป็น Fish return `${animal.name} กำลังว่ายน้ำ`; } animal.fly(); // TS รู้ว่าเป็น Bird return `${animal.name} กำลังบิน`;}Custom Type Guards (is)
Section titled “Custom Type Guards (is)”เมื่อ built-in guards ไม่เพียงพอ สร้าง custom guard ด้วย keyword is:
interface Admin { role: "admin"; permissions: string[];}
interface RegularUser { role: "user"; subscription: string;}
type AppUser = Admin | RegularUser;
// Custom type guard — return type เป็น "user is Admin"function isAdmin(user: AppUser): user is Admin { return user.role === "admin";}
function renderDashboard(user: AppUser): string { if (isAdmin(user)) { // TS รู้ว่าเป็น Admin — เข้าถึง permissions ได้ return `Admin Dashboard: ${user.permissions.length} permissions`; } // TS รู้ว่าเป็น RegularUser return `User Dashboard: Plan ${user.subscription}`;}Assertion Functions
Section titled “Assertion Functions”// Assertion function — throw ถ้าไม่ผ่านfunction assertIsString(value: unknown): asserts value is string { if (typeof value !== "string") { throw new Error(`Expected string, got ${typeof value}`); }}
function processInput(input: unknown): string { assertIsString(input); // หลังจาก assert — TS รู้ว่า input เป็น string return input.toUpperCase();}
// Non-null assertionfunction assertDefined<T>(value: T | null | undefined): asserts value is T { if (value === null || value === undefined) { throw new Error("Value is null or undefined"); }}Combining Guards
Section titled “Combining Guards”interface SuccessResponse { ok: true; data: unknown; }interface ErrorResponse { ok: false; error: string; }type Response = SuccessResponse | ErrorResponse;
function isSuccess(res: Response): res is SuccessResponse { return res.ok === true;}
function isStringArray(data: unknown): data is string[] { return Array.isArray(data) && data.every(item => typeof item === "string");}
function processResponse(res: Response): string[] { if (!isSuccess(res)) { throw new Error(res.error); } if (!isStringArray(res.data)) { throw new Error("Expected string array"); } return res.data; // type: string[]}