Type guards are special functions in TypeScript that help you narrow down the type of a variable within a block of code. They are one of the best ways to type check your code without using type assertions.
Type guards are defined using the is
keyword. Here's an example:
function isString(value: unknown): value is string {
return typeof value === "string";
}
const value: unknown = "Hello, world!";
if (isString(value)) {
console.log(value.toUpperCase());
// ^ value: string
}
In the above example, isString
is a type guard that checks if the value
is a string. If the check passes, TypeScript narrows down the type of value
to string
.
Type guards can also be used in array.filter
to narrow down the type of elements in an array:
function isString(value: unknown): value is string {
return typeof value === "string";
}
const values: unknown[] = ["Hello", 42, "world", 100];
const strings = values.filter(isString);
// ^ strings: string[]
There are some built-in type narrowing expressions in TypeScript that you can use:
typeof
type guards - checks the type of a variable using thetypeof
operatorinstanceof
type guards - checks if a variable is an instance of a classin
type guards - checks if a property exists in an objectArray.isArray
type guards - checks if a variable is an array===
- checks if a variable is a literal value
These are illustrated in the following examples:
// Type guard for primitives
function doSomethingToString(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
// ^ value: string
}
}
// Type guard for classes
function parseDate(value: Date | string) {
if (value instanceof Date) {
return date;
// ^ value: Date
}
return new Date(value);
}
// Type guard for objects
function printName(value: unknown) {
if ("name" in value) {
console.log(value.name);
// ^ value: { name: string }
}
}
// Type guard for arrays
function double(value: number | number[]) {
if (Array.isArray(value)) {
return value.map((x) => x * 2);
// ^ value: number[]
}
return value * 2;
}
Type guards and type narrowing expressions work is due to TypeScript's control flow analysis.
TypeScript analyzes the flow of your code and intelligently narrows down the type of a variable based on the conditions that are met.
This is also how the type narrowing worked in the previous chapter of literal unions:
// Type narrowing of literal unions
function move(to: 'start' | 'end') {
if (to === 'start') {
console.log(to);
// ^ to: 'start'
} else {
// to: 'end'
}
}
// Type narrowing using type guards
type User = { name: string };
function isUser(user: unknown): user is User {
return typeof user === "object" && "name" in user;
}
function processData(data: unknown) {
if (isUser(data)) {
console.log(data.name);
// ^ data: { name: string }
}
}