TypeScript

TypeScript #

Properly handle unknown errors #

interface ErrorWithCode extends Error {
    code: string;
}

function isErrorWithCode(err: unknown): err is ErrorWithCode {
    if (err instanceof Error) {
        if ('code' in err)) {
            return true;
        }
    }
    
    return false;
}

function doSomething(key: string) {
    try {
     ...
    } catch (ex) {
        if (isErrorWithCode(ex) && ex.code === 'ENOENT') {
          // do something
        } else {
            throw ex;
        }
    }

Get properties from unknown objects #

function getName(something: unknown) {
    let name: string | undefined;
    
    if (typeof something === 'object' && something !== null && 'name' in something) {
        if (typeof something.name === 'string') {
            name = something.name;
        }
    }
    
    return name;
}

Switch expression #

switch(expression) {
    case constant-expression1: {
        //statements;
        break;
    }
    case constant_expression2: {
        //statements;
        break;
    }
    default: {
        //statements;
        break;
    }
}

Delete generated js files #

tsc --build --clean

TypeScript ? question mark - optional chaining #

const test = undefined;
console.log(test.length)
// TypeError: Cannot read properties of undefined (reading 'length')
const test = undefined;
console.log(test?.length)
// undefined

TypeScript project references , nested tsconfig.json #

  • Nested tsconfigs do NOT automatically assume different projects
  • By adding references in the root project, you instruct tsc to build first the referenced projects and then the root one
  • The root tsconfig should exclude the directories of the referenced projects and the child projects should stop the exclusion on their own tsconfig
  • Referenced projects should have ‘composite’: true and noEmit: false in their tsconfigs
  • Project references

TypeScript - improve performance #

tsconfig.json types array not getting used #

Delete the tsconfig.tsbuildinfo cache file!

Use of never for exhaustiveCheck #

function doSwitch(value: MyEnum): boolean {
    switch (value) {
        case MyEnum.A: return true;
        case MyEnum.B: return true;
        default: {
            // Use never type here
            const exhaustiveCheck: never = value;
            throw new Error(exhaustiveCheck);
        }
    }
}

Assign a property on constructor #

export class Lock {
    constructor(private lockName: string) {
    }
}

Assert not null with typescript typeguard #

function expectNotNull<T>(value: T): asserts value is NonNullable<T> {
    expect(value).not.toBeNull();
}

Assert not undefined with typescript typeguard #

function expectNotUndefined<T>(value: T): asserts value is NonUndefined<T> {
    if (value === undefined) {
        throw new Error('Value is undefined');
    }
}

type NonUndefined<T> = T extends undefined ? never : T;

ESM modules error: CustomError: Cannot find module … imported from … #

Add the “.js” NOT “.ts” extension in the import, i.e. import { EfficientQueue } from './EfficientQueue.js';

String comparator for sorting #

String.localCompare()

const items = ["réservé", "premier", "communiqué", "café", "adieu", "éclair"];
items.sort((a, b) => a.localeCompare(b));

Convert string array to union type #

const myVals = ['A', 'B'] as const;

type myUnionType = typeof myVals[number];

Convert a property of an object array to union type #

const colors = [
    { no: 1, name: 'Red' },
    { no: 2, name: 'Green' },
    { no: 3, name: 'Blue' },
] as const;

type ColorName = typeof colors[number]['name']; // 'Red' | 'Green' | 'Blue'

Convert string array to object keys #

const myVals = ['a', 'b'] as const;

type myObj = {
    [P in (typeof myVals)[number]]: boolean;
};

Convert object keys to union type #

const yesNo = {
    yes: 'Yes',
    no: 'No',
} as const;

type YesNo = typeof yesNo[keyof typeof yesNo];

Convert object keys to array #

const yesNo = {
    yes: 'Yes',
    no: 'No
} as const;

const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>

const yesNoArr = getKeys(yesNo);

TypeScript Object vs {} vs object #

TypeScript has three confusing types: Object, {} and object.

You can assign null and undefined to all three types if strictNullChecks compiler option is disabled otherwise the compile error occurs.

  • Object: Contains stuff (like toString(), hasOwnProperty()) that is present in all JavaScript objects. Any value (primitive, non-primitive) can be assigned to Object type.
  • {}: {} is an empty object. It is pretty much the same as Object in runtime but different in compile time. In compile time {} doesn’t have Object’s members and Object has more strict behavior (see the @golmschenk’s comment).
  • object: object was introduced in TypeScript 2.2. It is any non-primitive type. You can’t assign to it any primitive type like bool, number, string, symbol.

Differentiating between types in a union based on a key #

Use the ‘in’ operator as a type guard

interface Car {
    color: string;
    doors: number;
    speed: number;
    bootSize: number;
}

interface Bike {
    color: string;
    speed: number;
    type: string;
}

function doSomething(vehicle: Car | Bike) {
    if ('doors' in vehicle) {
        console.log(vehicle.bootSize);
    } else {
        console.log(vehicle.type);
    }
}

Destructuring #

Destructuring to new variables #

let { r, g, b } = colors;

Destructuring to new variables with different names #

let { r: red, g: green, b: blue } = colors;

Destructuring to existing variables #

let r, g, b;
({ r, g, b } = colors);

Destructuring to existing variables with different names #

let red, green, blue;
({ r: red, g: green, b: blue } = colors);

Destructuring into another object with same property names #

let myColor = { r: 0, g: 0, b: 0 };..
({ r: myColor.r, g: myColor.g, b: myColor.b } = colors);

Destructuring into another object with different property names #

let myColor = { red: 0, green: 0, blue: 0 };.
({ r: myColor.red, g: myColor.green, b: myColor.blue } = colors);

Array destructuring #

const RGB = [255, 180, 151];
const [red, green, blue] = RGB;

Destructure, Skip the first item and set default value #

const RGB = [255, 180];
// Set a default value (51) for the third item (`blue`)
const [, green, blue = 51] = RGB;

Access nested properties #

const student = {
    name: 'John Doe',
    age: 16,
    scores: {
        maths: 74,
        english: 63
    }
};

const { name, scores: {maths, science = 50} } = student;

Swap variables #

let width = 300;
let height = 400;

[width, height] = [height, width];

Nested array destructuring #

const color = ['#FF00FF', [255, 0, 255], 'rgb(255, 0, 255)'];
const [hex, [red, green, blue]] = color;

Rest items #

const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
const [red,, yellow, ...otherColors] = rainbow;
console.log(otherColors); // ['green', 'blue', 'indigo', 'violet']

Running esm scripts using ts-node #

In tsconfig.json

"ts-node": {
    "moduleTypes": {
        "./my-script.ts": "esm"
    }
}

To execute

node --loader ts-node/esm ./my-script.ts

Utility types libraries #

Other resources #

TypeScript Training #