Typescript basics

Krzysztof Kowalczyk
Dec 23 '18 · 6 min read · 792 views

Configuration

Enable noImplicitAny and strictNullChecks

Primitive types

Numbers
let num: number;

num = 123;
num = 123.456;
num = '123'; // Error
Interfaces
interface Person {
    firstName: string;
    middleName?: string; // optional member
    lastName: string;
    age: number;
}
// OK
let person1: Person;
person1 = {
    firstName: "John",
    // not an error, because middleName is optional
    lastName: "Doe",
    age: 42,
};

// Error
person1 = {
    firstName: "John",
    lastName: "Doe",
    age: "42", // Error! wrong type
};

// Error
person1 = {
    firstName: "John",
    middleName: "V",
    // Error! lastName is missing
    age: 42,
};
Booleans
let bool: boolean;

bool = true;
bool = false;
bool = 'false'; // Error
bool = 1; // Error
Strings
let str: string;

str = '123';
str = 123; // Error
Arrays
let strArray: string[];

strArray = ["hello", "world"];
console.log(strArray[0]); // 'hello'

strArray[1] = "lafayette"; // OK
strArray = ["goodbye", "cruel", "world"]; // OK

strArray[0] = false; // Error!
strArray = "hello world"; // Error!
strArray = [42, "world"]; // Error!

Strict Object Literal Checking

function logName(something: { name: string }) {
    console.log(something.name);
}

var person = { name: "ryan", job: "being awesome" };
var animal = { name: "cow", diet: "vegan" };
var random = { note: "I don't have a name property" };

logName(person); // OK
logName(animal); // OK
logName(random); // Error: property `name` is missing

Allowing Extra Properties

let myObject: {
    mustBeNumber: number;
    [index: string]: any; // index signature!
};

// OK, `extra` matched by index signature
myObject = { mustBeNumber: 1, extra: true }; 
// Error!, `mustBeNumber` must be a number
myObject = { mustBeNumber: true, extra: 'a string' };

Classes & Interfaces

interface Point {
    x: number;
    y: number;
    add: () => number;
}

class MyPoint implements Point {
    x: number;
    y: number;
    // etc

    add() {
        return this.x + this.y;
    }
}

any

let vehicle: any;

// Takes all types
vehicle = '123';
vehicle = 123;
vehicle = { make: 'ford', model: 'bronco' };

// Is compatible with all types
let num: number;
vehicle = num;
num = vehicle;

null, undefined & void

let mileage: number;
let model: string;

// These literals can be assigned to any type
mileage = null;
mileage = undefined;
model = null;
model = undefined;

// void signifies that the function returns nothing
function logger(message: string): void {
    console.log(message);
}

Unions

// this function accepts a string OR an array of strings
function formatCommandline(command: string | string[]) {
    let line: string;
    if (typeof command === "string") {
        line = command.trim();
    } else {
        line = command.join(" ").trim();
    }

    // Do stuff with line, which is always string
}

Type Aliases

type Coordinates = { lat: number; long: number };

let myLocation: Coordinates;

// OK
myLocation = { lat: 48.423, long: -89.74 };
// Error!
myLocation = { lat: 48.423, long: null };

Functions

function doStuff(word: string, numRepeats: number): string {
    return word.repeat(numRepeats);
    // return numRepeats; // Error!
    // return false; // Error!
}

doStuff("AcadianaSoftwareGroup", 5); // OK
doStuff("AcadianaSoftwareGroup"); // Error!
doStuff(42, 3); // Error!
interface Person {
    fullName: string;
    age: number;
}

function makePerson(firstName: string, lastName: string, age: number, salutation?: string): Person {
    return {
        fullName: `${salutation || ""} ${firstName} ${lastName}`.trim(),
        age: age,
    };
    // Error!
    // return {
    //     firstName: firstName,
    //     age: age,
    // };
}

const jWright = makePerson("Johnathon", "Wright", 28); // OK
const godfather = makePerson("Chris", "Parich", 24, "Sir"); // OK
const dSmith = makePerson("Davin", 34, "Smith"); // Error!
const aSonge = makePerson("Alex", "Songe"); // Error!

Function Overloads

function padding(a: number, b?: number, c?: number, d?: any) {
    if (b === undefined && c === undefined && d === undefined) {
        b = c = d = a;
    } else if (c === undefined && d === undefined) {
        c = a;
        d = b;
    }
    return {
        top: a,
        right: b,
        bottom: c,
        left: d,
    };
}
// Overloads
function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);

// Function body
function padding(a: number, b?: number, c?: number, d?: number) {
    if (b === undefined && c === undefined && d === undefined) {
        b = c = d = a;
    } else if (c === undefined && d === undefined) {
        c = a;
        d = b;
    }
    return {
        top: a,
        right: b,
        bottom: c,
        left: d,
    };
}

padding(1); // OK: all
padding(1, 1); // OK: topAndBottom, leftAndRight
padding(1, 1, 1, 1); // OK: top, right, bottom, left
padding(1, 1, 1); // Error: Not a part of the available overloads

DefinitelyTyped

$ npm install —save @types/lodash
After that, use the library as you would normally, but with type safety!
import * as _ from "lodash";

Type Inference: Variables

// mileage is INFERRED to be a number
let mileage = 45692;
// make is INFERRED to be a string
let make = "Tesla";

mileage = make; // Error! cannot assign `string` to a `number`

Type Inference: Return Type

// inferred as returning a number
function add(a: number, b: number) {
    return a + b;
}

// inferred as returning a string
function doStuff(word: string, numRepeats: number) {
    return word.repeat(numRepeats);
}

Type Inference: Structuring

// objects
let foo = {
    a: 123,
    b: 456,
};
foo.a = "hello"; // Error! cannot assign `string` to a `number`

// arrays
const bar = [1, 2, 3];
bar[0] = "hello"; // Error! cannot assign `string` to a `number`

Enums

enum SocialNetworkType {
    Twitter,
    Facebook,
    Google,
}

// Sample Usage
let type = SocialNetworkType.Facebook;

// Safety
type = 'not in enum'; // Error!

Enums are number based

enum SocialNetworkType {
    Twitter,     // 0
    Facebook,    // 1
    Google,      // 2
}

let type = SocialNetworkType.Facebook;

// Effectively the same as SocialNetworkType.Facebook
type = 1;

String enums

export enum SocialNetworkType {
    Twitter = "twitter",
    Facebook = "facebook",
    Google = "google",
}

let type = SocialNetworkType.Google;

type = SocialNetworkType.Facebook; // OK
type = 'facebook'; // Error!
class Queue {
    private data = [];
    push = item => this.data.push(item);
    pop = () => this.data.shift();
}

// Usage
const queue = new Queue();
queue.push("hello");
queue.push("world");
queue.pop(); // 'hello'
queue.pop(); // 'world'
const queue = new Queue();
queue.push(1);
queue.push("42"); // oops, we made a mistake

// sometime later...
queue.pop().toPrecision(1); 
queue.pop().toPrecision(1); // RUNTIME ERROR

Generics

class Queue<T> {
    private data = [];
    push = (item: T) => this.data.push(item);
    pop = (): T => this.data.shift();
}

// Usage
const queue = new Queue<number>();
queue.push(0);
queue.push("1"); // Error! cannot push a string. Only numbers allowed

Type Assertions

interface Person {
    age: number;
    name: string;
}

let mike = {};
mike.age = 54; // Error: property 'age' does not exist on `{}`
mike.name = "Michal"; // Error: property 'name' does not exist on `{}`

// OK with type assertion
let joe = {} as Person;
joe.age = 37;
joe.name = "Joseph";

Type assertion is dangerous

interface Person {
    age: number;
    name: string;
}

let joe = {} as Person; // #yolo

// sometime later...
joe.name.toLowerCase(); // RUNTIME ERROR!

Type Guards

function doCoolStuff(numberOrString: number | string) {
    // typeof type guard
    if (typeof numberOrString === "string") {
        // Within the block TS knows that x is a string
        console.log(Math.round(numberOrString)); // Error!
        console.log(numberOrString.toLowerCase()); // OK
    }

    // Error! Ouside the block, there is no guarantee that x is a `string`
    console.log(numberOrString.toLowerCase(1));
}
class Person {
    ethnicity: "white";
    lives = 1;
}

class Cat {
    breed: "tabby";
    lives = 9;
}

function doCoolStuff(arg: Person | Cat) {
    // instanceof with else type guard
    if (arg instanceof Person) {
        console.log(arg.ethnicity); // OK
        console.log(arg.breed); // Error!
    } else {
        // TS knows this MUST be Cat
        console.log(arg.ethnicity); // Error!
        console.log(arg.breed); // OK
    }
}
class Person {
    kind: "person";
    ethnicity: "white";
    lives = 1;
}

class Cat {
    kind: "cat";
    breed: "tabby";
    lives = 9;
}

function doCoolStuff(arg: Person | Cat) {
  // Literal type guard
  if (arg.kind === "person") { 
        console.log(arg.ethnicity); // OK
        console.log(arg.breed); // Error!
    } else {
        // TS knows this MUST be Cat
        console.log(arg.ethnicity); // Error!
        console.log(arg.breed); // OK
    }
}
class Person {
    ethnicity: "white";
    lives = 1;
}

class Cat {
    breed: "tabby";
    lives = 9;
}

function isPerson(arg: any): arg is Person {
    return arg.ethnicity !== undefined;
}

function doCoolStuff(arg: Person | Cat) {
    // user-defined type guard
    if (isPerson(arg)) {
        console.log(arg.ethnicity); // OK
        console.log(arg.breed); // Error!
    } else {
        // TS knows this MUST be Cat
        console.log(arg.ethnicity); // Error!
        console.log(arg.breed); // OK
    }
}
Updating...

Share on