genkio / blog

Stay hungry stay foolish
https://slashbit.github.io/blog/
0 stars 1 forks source link

Understanding TypeScript #187

Open genkio opened 5 years ago

genkio commented 5 years ago

study notes taken from the Understanding TypeScript course

Installation

npm -g install typescript

Initialize project

Create tsconfig.json

tsc --init

Types

Basic type

let myName:string = 'Joe';

Array that has elements of type any

let hobbies: any[] = ['Coding', 'Reading'];

Tuple

let address: [string, number] = ['Some address', 1];

Enum

enum Color { Blue, Grey, Green };
let myColor: Color = Color.Blue;
console.log(myColor); // 0

enum myColor { Blue = 100, Grey = 200, Green = 300 };

Function with return type

function returnMyName(): string {
    return 'Joe';
}

Function with no return value

function returnNothing: void {
    console.log('Hello');
}

Function with argument types

funciton multiply(value1: number, value2: number): number {
    return value1 * value2;
}

Function types

let myMultiply: (a: number, b: number) => number
myMultiply = multiply;

Objects and types

let userData: { name: string, age: number } = {
    name: 'Joe',
    age: 30
};

Complex object types

let complex: { data: number[], output: (all: boolean) => number[] } = {
    data: [100, 3.99, 10],

    output: function (all: boolean): number[] {
        return this.data;
    }
};

Type alias

type Complex = { data: number[], output: (all: boolean) => number[] }

let complex: Complex = {
    data: [100, 3.99, 10],

    output: function (all: boolean): number[] {
        return this.data;
    }
};

Union types

let myAge: number | string = 30;
myAge = '30';

Check types

let value = 30;
if (typeof value == 'number') {
    // do something about it
}

Never type

function neverReturn(): never {
    throw new Error('An error!');
}

Nullable types

let canBeNull: number | null = 12;
canBeNull = null;

Classes

Basics

class Person {
    name: string;
    private type: string;
    protected age: number = 30;

    constructor(name: string, public username: string) {
        this.name = name;
    }

    printAge() {
        console.log(this.age);
    }

    // only way to write private variable
    setType(type: string) {
        this.type = type;
        console.log(this.type);
    }
}

const person = new Person('Joe', 'joe');
console.log(person); // { name: 'Joe', username: 'joe' }
person.printAge();
person.setType('Cool guy');

Inheritance

class Joe extends Person {
    constructor(username: string) {
        super('Doe', username);
        this.age = 31;
    }
}

const doe = new Joe('doe')
console.log(doe);

Getters & setters

class Plant {
    private _species: string;

    get species() {
        return this._species;
    }

    set species(value: string) {
        if (value.length > 3) {
            this._species = value;
        } else {
            this._species = 'Default';
        }
    }
}

let plant = new Plant();
plant.species = 'Green Plant';

Static properties & methods

class Helpers {
    static PI: number = 3.14;

    static calcCircumference(diameter: number): number {
        return this.PI * diameter;
    }
}
console.log(2 * Helpers.PI);
console.log(Helpers.calcCircumference(8));

Abstract classes

abstract class Project {
    projectName: string = 'Default';
    budget: number;

    abstract changeName(name: string): void;

    calcBudget() {
        return this.budget * 2;
    }
}

class ITProject extends Project {
    changeName(name: string): void {
        this.projectName = name;
    }
}

const newProject = new ITProject();

Implement singleton with private constructors

class OnlyOne {
    private static instance: OnlyOne;

    private constructor(public name: string) {}

    static getInstance() {
        if (!OnlyOne.instance) {
            OnlyOne.instance = new OnlyOne('The Only One');
        }
        return OnlyOne.instance;
    }
}

const ins = OnlyOne.getInstance();

Readonly property

class OnlyOne {
    private static instance: OnlyOne;
    public readonly name: string;

    private constructor(name: string) {
        this.name = name;
    }

    static getInstance() {
        if (!OnlyOne.instance) {
            OnlyOne.instance = new OnlyOne('The Only One');
        }
        return OnlyOne.instance;
    }
}

const ins = OnlyOne.getInstance();
ins.name = 'error'; // compile error

Namespaces and Modules

Namesapces

namespace MyMath {
    const PI = 3.14;

    export function calcCircumference(diameter: number) {
        return diameter * PI;
    }
}

console.log(MyMath.calcCircumference(10))

Import namespaces

/// <reference path="circleMath.ts" />

Nested namespaces

namespace MyMath {
    export namespace Circle {
        const PI = 3.14;

        export function calcCircumference(diameter: number) {
            return diameter * PI;
        }
    }
}

console.log(MyMath.Circle.calcCircumference(10))

// or use shortcut
import CircleMath = MyMath.Circle;
console.log(CircleMath.calcCircumference(10))

Modules

// circle.ts
export const PI = 3.14;

export function calcCircumference(diameter: number) {
    return diameter * PI;
}

// index.ts
import { PI, calcCircumference } from './math/circle';

Interfaces

interface NamedPerson {
    firstName: string;
    age?: number;
    [propName: string]: any;

    greet(lastName: string): void;
}

function greet(person: NamedPerson) {
    console.log('Hello ', person.firstName);
}

const person: NamedPerson = {
    firstName: 'Joe',
    hobbies: ['Cooking', 'Reading'],

    greet(lastName: string) {
        console.log('Hi, I\'m ' + this.firstName + ' ' + lastName);
    }
};

greet(person);
person.greet('Doe');

Implement an interface

class Person implements NamedPerson {
    firstName: string;
    greet(lastName: string) {
        console.log('I\'m ' + lastName);
    };
}

const myPerson = new Person();
myPerson.firstName = 'Joe';
myPerson.greet('Doe');

Interface for function type

interface DoubleValueFunc {
    (number1: number, number2: number): number;
}

let myDoubleFunction: DoubleValueFunc;
myDoubleFunction = function(value1: number, value2: number) {
    return (value1 + value2) * 2;
};

Interface inheritance

interface AgedPerson extends NamedPerson {
    age: number;
}

const oldPerson: AgedPerson = {
    age: 30,
    firstName: 'Joe',
    greet(lastName: string) {
        console.log('Hello');
    }
};

Generics

function echo<T>(data: T) {
    return data;
}

console.log(echo('Joe').length);
console.log(echo(30).length); // compile error

Built-in generics

const results: Array<number> = [1.94, 2];
results.push(-2.99);
results.push('string'); // compile error

function printAll<T>(args: T[]) {
    args.forEach((element) => console.log(element));
}

printAll<string>(['Apple', 'Orange']);

Generic types

const echo2: <T>(data: T) => T = echo;
console.log(echo2<string>('Something'));

Generic class

class SimpleMath<T extends number> {
    baseValue: T;
    multipleValue: T;
    calculate(): number {
        return this.baseValue * this.multipleValue;
    }
}

const simpleMath = new SimpleMath<number>();
simpleMath.baseValue = 'something'; // compile error
simpleMath.multipleValue = 20;
console.log(simpleMath.calculate());

Use more than one generic types

class SimpleMath<T extends number | string, U extends number | string> {
    baseValue: T;
    multipleValue: U;
    calculate(): number {
        return this.baseValue * this.multipleValue;
    }
}

const simpleMath = new SimpleMath<string, number>();
simpleMath.baseValue = '10';
simpleMath.multipleValue = 20;
console.log(simpleMath.calculate());

Put it together

class MyMap<T> {
    private map: {[key: string]: T} = {};

    setItem(key: string, item: T) {
        this.map[key] = item;
    }

    getItem(key: string) {
        return this.map[key];
    }

    clear() {
        this.map = {};
    }

    printMap() {
        for (let key in this.map) {
            console.log(key, this.map[key]);
        }
    }
}

const numberMap = new MyMap<number>();
numberMap.setItem('apples', 10);
numberMap.setItem('oranges', 2);

const stringMap = new MyMap<string>();
stringMap.setItem('apples', '10');
stringMap.setItem('oranges', '2');

Decorators

function logged(constructorFn: Function) {
    console.log(constructorFn);
}

@logged
class Person {
    constructor() {
        console.log('Hi');
    }
}

Decorator factory

function logging(value: boolean) {
    return value ? logged: null;
}

@logging(true)
class Car {
    constructor() {
        console.log('Hi');
    }   
}

Use decorator to enrich class instance

function printable(constructorFn: Function) {
    constructorFn.prototype.print = function() {
        console.log(this);
    }
}

@printable
class Plant {
    name = 'Green Plant';
}

const plant = new Plant();
(<any>plant).print();

Method decorator

function editable(value: boolean) {
    return function(target: any, propName: string, descriptor: PropertyDescriptor) {
        descriptor.writable = value;
    }
}

class Project {
    projectName: string;

    constructor(name: string) {
        this.projectName = name;
    }

    @editable(false)
    calcBudget() {
        console.log(1000);
    }
}

const project = new Project('Super Project');
project.calcBudget();
project.calcBudget = function() {
    console.log('2000'); // will not work (edit)
}
project.calcBudget();

Parameter decorator

function printInfo(target: any, methodName: string, paramIndex: number) {
    console.log('Hello');
}

class Course {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    printStudentNumbers(mode: string, @printInfo printAll: boolean) {
        if (printAll) {
            console.log(1000);
        } else {
            console.log(2000);
        }
    }
}

const course = new Course();
course.printStudentNumbers('anything', true);
course.printStudentNumbers('anything', false);