Skip to content

Type Guards ใน TypeScript

Type Guards คือเทคนิคที่ช่วย narrow (จำกัด) type ลง ณ runtime ทำให้ TypeScript รู้ว่า ณ จุดนั้นข้อมูลเป็น type อะไร

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"
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(", ")}`;
}
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} กำลังบิน`;
}

เมื่อ 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 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 assertion
function assertDefined<T>(value: T | null | undefined): asserts value is T {
if (value === null || value === undefined) {
throw new Error("Value is null or undefined");
}
}
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[]
}