Skip to content

Latest commit

 

History

History
197 lines (154 loc) · 4.71 KB

Type-guards.md

File metadata and controls

197 lines (154 loc) · 4.71 KB
tree_title description last_modified
Type guards
An overview of how to use TypeScript type guards, including creating your own custom ones
2020-10-17 18:41:29 UTC

Type guards (TypeScript)

Contents

Basic idea

  • Test, at runtime, whether a certain value is of a certain type
  • TypeScript compiler can use this information to make better assumptions

Built-in type guards

typeof type guards

function test(input: string | string[]) {
    input.split(""); // compiler error
    input.filter(() => true); // compiler error

    if (typeof input === "string") {
        // input has type string here
        return input.split("");
    } else {
        // input has type string[] here
        return input.filter(() => true);
    }
}

instanceof type guards

class ClassA {
    methodA() {
        return true;
    }
}

class ClassB {
    methodB() {
        return true;
    }
}

function test(instance: ClassA | ClassB) {
    instance.methodA(); // compiler error
    instance.methodB(); // compiler error

    if (instance instanceof ClassA) {
        // instance has type ClassA here
        instance.methodA();
    } else {
        // instance has type ClassB here
        instance.methodB();
    }
}

Type guards based on common property

Can be used to implement Discriminating Unions (see Discriminating Unions):

  • Types with common property indicating type
  • A type alias that is the union of these types
  • Type guards on the common property

Example:

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(shape: Shape) {
    switch (shape.kind) {
        case "square":
            // shape has type Square here
            return shape.size * shape.size;
        case "rectangle":
            // shape has type Rectangle here
            return shape.height * shape.width;
        case "circle":
            // shape has type Circle here
            return Math.PI * shape.radius ** 2;
    }
}

TypeScript recognizes that the common property kind determines the type here

Also works with regular if-statements:

function test(shape: Shape) {
    if (shape.kind === "rectangle") {
        // shape has type Rectangle here
        console.log(shape.height);
    }
}

Type guards based on available properties

Example code (uses the interfaces from the above example):

function test(shape: Shape) {
    if ("radius" in shape) {
        // shape has type Circle here
    }
}

User-defined type guards

You can also define your own type guards that perform the type check at runtime by looking at certain properties of the object you receive

interface Message {
    messageType: string;
}

interface UserMessage extends Message {
    messageType: "user";
    userId: number;
}

interface OrderMessage extends Message {
    messageType: "order";
    orderId: number;
}

function isMessage(arg: any): arg is Message {
    return typeof arg.messageType === "string";
}

function isUserMessage(arg: Message): arg is UserMessage {
    return arg.messageType === "user";
}

function isOrderMessage(arg: Message): arg is OrderMessage {
    return arg.messageType === "order";
}

function test(input: object) {
    console.log(input.messageType); // compiler error

    if (isMessage(input)) {
        // input has type Message here

        console.log(input.messageType);
        console.log(input.userId); // compiler error
        console.log(input.orderId); // compiler error

        if (isUserMessage(input)) {
            // input has type UserMessage here
            console.log(input.userId);
        } else if (isOrderMessage(input)) {            
            // input has type OrderMessage here
            console.log(input.orderId);
        }
    }
}

Resources