TypeScript advanced typing



Headlines
Conditional type

Rule(s)

Example

export abstract class Individual { …
export class Kid extends Individual { …

type Kid_bis = Kid extends Individual ? Kid : never; // 'Kid_bis' is constructed as a conditional type...
const Maternity = function (): Kid_bis { // 'Kid' and 'Kid_bis' are equivalent types...
    return new Kid();
}

type Individual_bis = Individual extends Kid ? Individual : never;
const Maternity_ = function (): Individual_bis { // 'Individual_bis' and 'never' are equivalent types...
    // One doesn't return a 'Individual' object since 'Individual' is abstract:
    // return new Kid(); // Compilation error since 'Maternity_' *never* returns something...
    // 'throw' creates compatibility with 'never':
    throw "TS2534: A function returning 'never' cannot have a reachable end point.";
}

Example

type Never_never<Any_type> = Any_type extends Object ? Any_type : never;
const b: Never_never<boolean> = true; // No compilation error!

Example Miscellaneous.ts.zip 

type Primitive = boolean | number | string | symbol; // Bug: this does not include *ALL* primitive types from the TypeScript perspective...
// let wm: WeakMap<Primitive, any>; // Compilation error: by construction, 'WeakMap' does not accept primitive types...
// type AdaptiveMap<Key, Value> = Key extends Primitive ? Map<Key, Value> : WeakMap<Key, Value>; // Compilation error: Type 'Key' does not satisfy the constraint 'object'...
type AdaptiveMap<Key, Value> = Key extends Object ? WeakMap<Key, Value> : Map<Key, Value>;
let am: AdaptiveMap<Event, any>; // 'WeakMap'
am.set(new Event(""), null);
let am_: AdaptiveMap<number, any>; // 'Map' -> This does not work with 'boolean' while *it works with other primitive types*, why?
am_.set(0, null);

infer keyword

Rule(s)

Example (infer) Miscellaneous.ts.zip 

type Array_element_type<T> = T extends (infer Element_type)[] ? Element_type : T;
let b: Array_element_type<boolean>; // 'boolean' is not an array type...
b = true;
let n: Array_element_type<Array<number>>; // 'Array<number>' is an array type...
n = 0;
// Fairly similar:
type What_is_inside_type<I> = I extends Array<infer Element_type> ? Element_type : never;
let s: What_is_inside_type<string>; // 'string' is not an array type...
// s.constructor; // Compilation error: Property 'constructor' does not exist on type 'never'...
let s_: What_is_inside_type<Array<HTMLElement>>; // 'Array<HTMLElement>' is an array type...
s_.constructor; // 'HTMLElement' constructor function...

Example (tuple type) Miscellaneous.ts.zip 

type Second<T> = T extends [infer F, infer S, ...unknown[]] ? S : never;
type Forname_Surname_IsFemale = [string, string, boolean];
type Surname = Second<Forname_Surname_IsFemale>; // 'string' type...
const Barbier: Surname = "Barbier";

See also

Lookup type (keyof)

Rule(s)

Example (base) Miscellaneous.ts.zip 

// Lookup type extracts type of property:
type EventTarget_or_null = Event["currentTarget"];  // Extracted type is 'EventTarget | null'...
const et: EventTarget_or_null = new EventTarget(); // No compilation error!

Example (keyof) Miscellaneous.ts.zip 

type Keys_of_Event = keyof Event; // "data" | "type" | ... as properties of 'Event' DOM type...
const me: Event = new Event("Franck's event");
let type_property: Keys_of_Event = "type";
window.alert(me[type_property]); // 'Event' objects effectively have a 'type' property... -> "Franck's event" is displayed...
// let property_does_not_exist: Keys_of_Event = "property_does_not_exist"; // Bingo! -> compilation error...

Example Miscellaneous.ts.zip 

function get_property<Indexed_type, Property_type extends keyof Indexed_type>(o: Indexed_type, p: Property_type): Indexed_type[Property_type] {
    return o[p];  // Inferred type (as return) is 'Indexed_type[Property_type]'
}
Utility type

Rule(s)

ReturnType<T> Miscellaneous.ts.zip 

Rule(s)

Example

class Kid extends Individual {
    …
    isResponsible(responsibility: Responsibility): boolean {
        return …;
    };
// 'typeof this.isResponsible' does not work:
    isNotResponsible(responsibility: Responsibility): ReturnType<typeof Kid.prototype.isResponsible> {
        return !this.isResponsible(responsibility);
    };
}
…
let Jules: Individual = new Kid();
let is_Jules_responsible_: ReturnType<typeof Jules.isResponsible> = Jules.isResponsible(some_responsibility);

ThisParameterType<T> Miscellaneous.ts.zip 

Rule(s)

Example

type Kid_bis_repetita = ThisParameterType<typeof Jules.isResponsible>;
let Julie: Kid_bis_repetita;

Omit<T, Ks>

Rule(s)

Example

type P = Omit<Prisoner, 'given_name' | 'surname'>;
// import * as _ from 'lodash';
const p: P = _.omit(prisoner, ['given_name', 'surname']);
<const> cast

Rule(s)

Example Lottery.ts.zip 

class Lottery {
    public static Draw(bonus_number: number, ...numbers: Array<number>) /* Return type is inferred... */ {
        return {
            bonus_number: bonus_number,
            date: Date.now(), // For simplicity
            draw: Array.from(numbers)
        }
    }

    public static Draw_(bonus_number: number, ...numbers: Array<number>) /* Return type is inferred... */ {
        return <const>{ // On object literal here…
            bonus_number: bonus_number,
            date: Date.now(), // For simplicity
            draw: Array.from(numbers)
        }
    }
}

let result = Lottery.Draw(6, 43, 12, 3, 32, 34, 22);
console.assert(result.draw[0] === 43);
result.date = Date.parse('10 Feb 2020 15:50:00 GMT');
result = Lottery.Draw_(6, 43, 12, 3, 32, 34, 22);
// 'result' inferred type is: '{ bonus_number: number, date: Date, draw: Array<number> }'
// so 'date' *CAN BE* changed because of inferred type:
result.date = Date.parse('10 Feb 2020 15:50:00 GMT');
// 'result_' inferred type is: '{ readonly bonus_number: number, readonly date: Date, readonly draw: Array }'
const result_ = Lottery.Draw_(6, 43, 12, 3, 32, 34, 22);
// so 'date' *CANNOT BE* changed because of inferred type:
// result_.date = Date.parse('10 Feb 2020 15:50:00 GMT'); // Compilation error!
Function type

Rule(s)

Example (call signature)

enum Comparable_ {LessThan, Equal, GreaterThan}

interface Comparable<T> { // Similar to Java "functional interface"
    (t: T): Comparable_; // Call signature
}
// Compiler error: "Type 'Giraffe' provides no match for the signature '(t: Giraffe): Comparable_'" (no solution exists!)
class Giraffe extends Mammal implements Comparable<Giraffe> {
    constructor(readonly _name: string, readonly _height: number) {
    }
}

Example (method signature)

enum Comparable_ {LessThan, Equal, GreaterThan}

interface Comparable<T> { // Similar to Java "functional interface"
    compareTo(t: T): Comparable_;
}

class Elephant extends Mammal implements Comparable<Elephant> {
    constructor(readonly _name: string, readonly _weight: number) {
        super();
    }

    compareTo(e: Elephant): Comparable_ { // 'public' by default
        if (this._weight < e._weight) return Comparable_.LessThan;
        if (this._weight === e._weight) return Comparable_.Equal;
        if (this._weight > e._weight) return Comparable_.GreaterThan;
    }
}

Rule(s)

Example

interface Comparator<T> {
    compare: (t1: T, t2, T) => Comparable_; // 'compare' has type '(t1: T, t2, T) => Comparable_'
}

class Zoo implements Comparator<Giraffe> {
    compare = function (g1: Giraffe, g2: Giraffe): Comparable_ {
        if (g1._height < g2._height) return Comparable_.LessThan;
        if (g1._height === g2._height) return Comparable_.Equal;
        if (g1._height > g2._height) return Comparable_.GreaterThan;
    };
}

Example

type Ready = (item: Open_Food_Facts_item) => void;
…
// Later on:
this._item_data = new Promise/*<Open_Food_Facts_item>*/((ready: Ready, problem) => { // 'problem' aims at being called when 'Promise' object has difficulty in achieving its job...
    …
    // 'ready' must be called with only one argument whose type is 'Open_Food_Facts_item'
    let offi: Open_Food_Facts_item = {} as Open_Food_Facts_item;
    …
    ready(offi);
});

Rule(s)

Example (type of a constructor function)

 // 'new' is the way for TypeScript to define the type signature of a constructor function:
type Constructor_function_type = new (bye: string, hello: string) => Greetings;

Generic function type

Example Inheritance_polymorphism.ts.zip 

let t_toString = <T>(t: T) => t.toString();

let identity = <T>(t: T): T => { // <=> 'function identity<T>(t: T): T {'
    return t;
}

let identity_alias: <U>(u: U) => U = identity; // Type of 'identity' and 'identity_alias' is '<T>(t: T) => T' <=> '<U>(u: U) => U'
…
// Calls later on:
window.alert("Zero: " + t_toString(0));
let result: string = identity("Franck Barbier"); // <=> 'identity<string>("Franck Barbier")'
result = identity_alias("Franck Barbier");
Assertion function

Rule(s)

Example Miscellaneous.ts.zip 

class Assertion_function_example {
    handle(e: MouseEvent | TouchEvent) { // Ideally...
        if (e instanceof MouseEvent) /* Do something */ return;
        e.touches; // By construction, 'e' complies with 'TouchEvent'
    }

    handle_(e: UIEvent /* MouseEvent | TouchEvent */) { // Legacy signature?
        if (e instanceof MouseEvent) /* Do something */ return;
        console.assert(e instanceof TouchEvent); // For test...
        if (e instanceof TouchEvent === false) throw TypeError("'TouchEvent' expected..."); // For run-time...
        (e as TouchEvent).touches; // Cast probably succeeds due to immediate prior checking...
    }

    _e_is_TouchEvent(e: UIEvent): asserts e is TouchEvent { // The notion of "assertion function" from TypeScript ver. 3.7...
        if (e instanceof TouchEvent === false) throw TypeError("'TouchEvent' expected...");
    }

    handle__(e: UIEvent /* MouseEvent | TouchEvent */) { // Use of TypeScript ver. 3.7 'asserts'
        if (e instanceof MouseEvent) /* Do something */ return;
        console.assert(e instanceof TouchEvent); // For test...
        this._e_is_TouchEvent(e);
        // This may occur if and only if immediate prior call succeeds:
        e.touches; // TypeScript deduces that 'e instanceof TouchEvent' from 'asserts e is TouchEvent'
    }
}
Decorator

Rule(s)

Example (method) Miscellaneous.ts.zip 

const is_configurable = (value: boolean) => { // Decorator for method...
    return (target: any, method_name: string, descriptor: PropertyDescriptor) => {
        console.assert(method_name === "my_method"); // See usage below...
        descriptor.configurable = value;
    };
}

class My_class {
    common_public_instance_method() {
        console.log("Common public instance method...");
    }

    @is_configurable(false) // Remove default configurable nature...
    my_method() {
        console.log("'my_method' cannot be suppressed from class' prototype...");
    }
}

const mc = new My_class();
console.assert(delete My_class.prototype.common_public_instance_method); // 'true' since public instance methods are configurable...
try {
    mc.common_public_instance_method(); // No longer exists so error...
} catch (error/*: TypeError*/) { // Type annotation not (yet?) allowed in TypeScript...
    console.log((error as TypeError).message);
}
console.assert(delete My_class.prototype.my_method === false); // Great, 'my_method' cannot be deleted...
mc.my_method(); // It works!

Example (class) Miscellaneous.ts.zip 

type Constructor_function_type = new (bye: string, hello: string) => Greetings; // 'new' is the way for TypeScript to define the type signature of a constructor function...

function Italian(constructor: Constructor_function_type) {
    // ECMAScript 2015 class expression (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/class)
    // const MyClass = class [className] [extends otherClassName] {
    //     // class body
    // };
    return class /* [className] */ extends constructor {
        constructor(bye = "Ciao", hello = "Pronto") {
            super(bye, hello); // No change...
        }
    }
}

@Italian
class Greetings {
    constructor(bye = "Bye", hello = "Hello") {
        console.log(hello + "... " + bye);
    }
}

new Greetings(); // Decorator is called here...