TypeScript by Example: Type Guards
Narrowing union types safely with TypeScript type guards in this comprehensive code example. Covers built-in typeof and instanceof checks, custom type predicates using the is keyword, and control flow analysis for accessing type-specific properties and methods.
Code
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function getSmallPet(): Fish | Bird {
// ... implementation ...
return { fly: () => {}, layEggs: () => {} } as Bird;
}
let pet = getSmallPet();
// User-defined type guard
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
if (isFish(pet)) {
pet.swim(); // TypeScript knows pet is Fish here
} else {
pet.fly(); // TypeScript knows pet is Bird here
}
// typeof guard
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}Explanation
Type guards allow you to narrow down the type of an object within a conditional block. TypeScript is smart enough to understand standard JavaScript checks like typeof and instanceof.
You can also define your own "user-defined type guards". These are functions whose return type is a type predicate in the form parameterName is Type. If the function returns true, TypeScript narrows the variable to that specific type in the corresponding block.
This feature is essential when working with Union types, as it allows you to safely access members that only exist on one of the possible types.
Code Breakdown
Fish | Bird creates a union type for the return value.pet is Fish is a user-defined type predicate.if (isFish(pet)) narrows pet to Fish type in this block.typeof id === "string" uses built-in type guard for primitives.
