genkio/blog

Understanding TypeScript

Opened this issue · 0 comments

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);