Skip to content

Advanced Types

Intersection types

  • combine multiple types into one type.

    type T1 = { a: string };
    type T2 = { b: string };
    type T3 = T1 & T2;
    const t3: T3 = { a: "a", b: "b" };
    
    type Combinable = string | number;
    type Numeric = number | boolean;
    type Universal = Combinable & Numeric; // string | number | boolean
    

Type guards

  • type guards are used to check if a value is of a certain type.
  • type guards using typeof operator
  • type guards using in operator
  • type guards using instanceof operator (only works with classes)
  • type guards using discriminated unions: specify a unique type for each branch of the union, then use a type guard to check if the value is of that type.

  • example:

type StringOrNumber = string | number;

function add(a: StringOrNumber, b: StringOrNumber) {
    if (typeof a === "string" || typeof b === "string") {
        // typeof operator
        return a.toString() + b.toString();
    }
    return a + b;
}

type T1 = { a: string };
type T2 = { b: string };
type T3 = T1 | T2;

function printType(t: T3) {
    if ("a" in t) {
        // in operator
        console.log("T1");
    } else if ("b" in t) {
        console.log("T2");
    }
}

class Person {
    name: string;
}

class Human {
    name: string;
}

function printType(p: Person) {
    if (p instanceof Person) {
        // instanceof operator
        console.log("Person");
    } else if (p instanceof Human) {
        console.log("Human");
    }
}
  • example, discriminated unions:
type A = {
    a: string;
    type: "A"; // discriminant property, only used to identify the type
};
type B = {
    b: string;
    type: "B"; // discriminant property
};
type C = A | B;

function f(c: C) {
    if (c.type === "A") {
        // discriminated union
        console.log(c.a);
    } else if (c.type === "B") {
        console.log(c.b);
    }
}

Type casting

  • we can cast a value to a different type.
  • useful to override a generic type to a more specific type.
const input = document.getElementById("input"); // HTMLElement | null
const input2 = document.getElementById("input")!; //HTMLElement

const input3 = <HTMLInputElement>input; // HTMLInputElement (type casting option 1), not suitable fo JSX
const input4 = input as HTMLInputElement; // HTMLInputElement  (type casting option 2)

Index properties

  • index properties are used to specify the type of object’s properties without knowing the actual keys.
  • if you used index properties, you can use the in operator to check if a property exists.
  • if you used index properties, you can use the keyof operator to get the keys of an object.
  • if you used index properties, you can use the typeof operator to get the type of a property.
  • if you used index properties, you can not have actual keys from a different type.
type Person = {
    name: string; // actual property
    [key: string]: string; // index properties
    age: number; // error, age is not a string
};

Function overloads

  • function overloads are used to specify different implementations of the same function.
type StringOrNumber = string | number;

function add(a: number, b: number): number; // function signature 1, if a and b are both numbers, return a number
function add(a: string, b: string): string; // function signature 2, if a and b are both strings, return a string
function add(a: StringOrNumber, b: StringOrNumber) {
    // actual function implementation
    if (typeof a === "string" || typeof b === "string") {
        return a.toString() + b.toString();
    }
    return a + b;
}

// without overloads
const x = add(1, 2); // 3, StringOrNumber
x.toString(); // error, toString does not exist on StringOrNumber

// with overloads
const x = (1, 2); // 3, number
const y = add("a", "b"); // "ab", string

Optional chaining

  • optional chaining is used to access properties of an object that may or may not exist.
  • this get compiled into javascript code as if statement that checks if the object exists and then accesses the property.
  • obj?.nestedObj?.property is the same as obj?.nestedObj?.property ?? undefined
  • obj?.nestedObj is the same as obj?.nestedObj || undefined

Nullish coalescing

  • nullish coalescing is used to access properties of an object that may or may not exist.
  • uses ?? operator.
  • works only if the left side is not null or undefined.
  • obj?.nestedObj?.property ?? defaultValue, defaultValue will be resolved if either nestedObj or nestedObj.property is null or undefined.