TypeScript
  • Introduction
  • Introduction
    • What is TypeScript?
    • Why TypeScript?
    • Setup
  • Beginner
    • Basic types
      • Primitive types
      • Arrays and tuples
      • Enums
      • Any type
    • Objects
    • Type aliases
    • Interfaces
    • Functions
      • Function signatures
      • Void type
      • Functions as types
    • Union types
    • Type guards
    • Intersection types
    • Nullable types
    • Inference
  • Resources
    • Resources
Powered by GitBook
On this page
  1. Beginner

Type guards

Normally in JavaScript, when we're unsure of a value's type, we check the existance of one of its members to determine what it is:

function getID(): string | number {
    return Math.random() > 0.5 ? "1" : 1;
}

let id = getID();

if (id.toUpperCase) {
    id = id.toUpperCase();
} else if (id.toFixed) {
    id = id.toFixed();
}

This code will throw the following TypeScript errors:

Property 'toUpperCase' does not exist on type 'string | number' Property 'toUpperCase' does not exist on type 'number' Property 'toFixed' does not exist on type 'string | number' Property 'toFixed' does not exist on type 'string'

We're getting these errors because we can only access members common to all types in a union, which id is. So, since id could be a string or a number, and since neither of the two share the toUpperCase or toFixed members, we get those errors.

If, however, we wanted to run id.toString(), we'd be fine, since both number and string have the toString method.

If we assert, however, that what we're checking is a string or a number, then we eliminate the errors:

// ...
if ((<string>id).toUpperCase) {
    id = (<string>id).toUpperCase();
} else if ((<number>id).toFixed) {
    id = (<number>id).toFixed();
}

Now that we've told TypeScript that in the case of a string, we want to check for the existence of the toUpperCase member and in the case of a number we want to check for toFixed, the complier is happy.

But this kind of granular assertion is extremely verbose and cumbersome. Luckily, we can inform TypeScript once in our first if statement. And by doing so, TypeScript is intellignet enough to know that by the time we reach the else if clause, id must be a number. In order to do this, we need to create a function calleda type guard:

function isString(val: string | number): val is string {
    return (<string>val).toUpperCase !== undefined;
}

Here, we've taken our if clause and put it in a function whose return value is a type predicate. Type predicates take the form: parameterName is Type.

Now, we can use isString in our code:

// ...
if (isString(id)) {
    id = id.toUpperCase();
} else {
    id = id.toFixed();
}

Once we've checked id's type using a type guard, TypeScript can be sure that in the if clause, id is a string and that in the else clause, it's a number. This will allow it to give you correct autocompletion in your editor and warn you if you try to use an incorrect method or property of id within the clause's block.

But what if we don't want to create a type guard function just to check a value's type? Fear not, there is a simpler way!

TypeScript recognizes the use of both typeof and instanceof as type guards. Allowing us to rewrite our code like so:

// ...
if (typeof id === 'string') {
    id = id.toUpperCase();
} else if (typeof id === 'number') {
    id = id.toFixed();
}

The only acceptable string values that can be used in a typeof type guard are "string", "number", "boolean", and "symbol".

We can also use instanceof in case we're dealing with instances of a class and not primitive values:

function getDate(): Date | number {
    return Math.random() > 0.5 ? new Date() : 17;
}

var dt = getDate();

if (dt instanceof Date) {
    console.log(dt.getFullYear());
} else {
    console.log(2000 + dt);
}

In this code, getDate() will give us either a Date object or a number representing the last two digits of the current year. If we try to add 2000 to a Date object, we get the following TypeScript error:

Operator '+' cannot be applied to types '2000' and 'Date'.

We need to figure out what type we're dealing with, and for that we use instanceof. Once we've narrowed down the value of dt to being an instance of Date, we can safely call getFullYear() on it, or add 2000 to the value in the the else clause.

PreviousUnion typesNextIntersection types

Last updated 6 years ago