# TypeScript basics

This post is recording the basics knowledge of TypeScript (TS).

1. What is TypeScript (TS)? Is a superset of JavaScript (JS), which is strongly typed language, which also eventually will complies the code down to the JavaScript level, because browser only understands JavaScript.

Essential concepts:

  1. Variable Types: [Basics: Boolean, Number, String]
  2. Functions
  3. Interfaces
  4. Class
  5. Access Modifiers

Before start, setup basics environment to run TypeScript code:

Please install TypeScript in your browser first by running this command:

// Install first
npm install -g typescript
// Then run version check
tsc -v

How to compile a .ts file to .js file (ts -> js)?

// eg: create a xxx.ts file first
// Then run command
tsc xxx.ts
// you will see js file has been generated as
// xxx.js
// The run this command to run js code:
node xxx.js
// we also can watch the changes by using:
ts xxx --watch

The reason of using types:

  1. Static type checking
  2. Give hint about what's going wrong?
  3. Intelligence suggestions, eg: if you define a number `num`, then you type `num.`, you will find the suggestions only related with numeric type of APIs

Here are the basics implementations for recording purpose only:

// When the time we see this warning: Cannot redeclare block-scoped variable 'message'.ts(2451)
// we add export {}; to resolve it
export {};
let message = "Ahoy, Damon !!";
console.log(message);

// Basic types
let name: string = 'damon'; // string
let isTrue: boolean = false; // boolean
let number: number = 1; // number

// Sub type can be used for initialize a variable or reassign the value to a declared variable
let n = null; // sub-type null
let u = undefined; // sub-type undefined
isTrue = n;
name = u;

// 2 ways to define array:
let arr1: number[] = [1,2,3];
let arr2: Array<number> = [1,2,3,4];

// Tuple type example:
// number of items inside array is fixed & order must be same as declarations
let mixArr: [string, number, boolean] = ['damon', 123, true];

// Enum:
enum Color {Red, Green, Blue};
let redColor: Color = Color.Red; // output is 0, because we haven't assigned value into each enum value
console.log('Enum red color define: ', redColor);
// assign value to each enum value:
enum ColorWithValue {Red="Red", Green="Green", Blue="Blue"};
console.log('Enum color with value: ', ColorWithValue.Green);

// When to use type 'any'?
// When the time we need to reassign value to different type, Eg:
let valueReadFromLibrary: any = 10;
valueReadFromLibrary = false;

// If we only use type 'any' WITHOUT any reassignment, typescript won't give us any error, which cause huge effort for code debugging, eg:
let noReassignTypeAny: any = 10;
// console.log(noReassignTypeAny()); // function, wrong (X)
console.log(noReassignTypeAny.name); // object, wrong (X)

// Type 'unknown':
let unknownVariableType: unknown = 10;
unknownVariableType = 'become a string';
// console.log(unknownVariableType.toUpperCase()); // If not define a type in advance, ts will give us error hint, so we need to predefine like this: 'variable as type'
console.log((unknownVariableType as string).toUpperCase());

// Multiple type variable:
let multipleTypeVariable: number | boolean;
multipleTypeVariable = 10;
multipleTypeVariable = false;

// Big topic: functions for TS
function sum(n1: number, n2?: number): number {
// ? means value is optional, can be fill in or leave it as empty
// the last: number means we defined the function sum output MUST be a number type value
  return n1 + n2;
};

console.log('n2 can be optional: ', sum(1)); // value is NaN !!!

// throw error:
// sum(1, true);
// good example

function preDefinedValueThenSum(n1: number, n2: number = 0): number {
  return n1 + n2;
};

console.log('n2 can be optional: ', preDefinedValueThenSum(1)); // value is 1 !!!

// Another big topic of Interface:
// We can specify the object as type in TypeScript -> Interface !!!!!!
// Use case:
function fullName(person: {firstName: string, lastName: string}) {
  return `${person.firstName}, ${person.lastName}`;
}

let p = {
  firstName: 'Damon',
  lastName: 'Wu'
};

console.log(fullName(p));

// So in above case, we see person: {firstName: string, lastName: string}, which might be repeated for later usage, so if we have function B, C D ..., we all need to defined person like this format, person: {firstName: string, lastName: string}, which is a bad practice, here comes with Interface !!!!

interface Person {
  firstName: string,
  lastName?: string
};

function fullNameWithInterface(person: Person) {
  return `${person.firstName}, ${person.lastName}`;
};

let p2: Person = {
  firstName: 'Ella',
};

console.log(fullNameWithInterface(p2));

// Just another topic: Class & Access Modifier
// OOP concept: inheritance:

class Employee { // ts class structure:
  employeeName: string; // class variable

  constructor(en: string) { // constructor
    this.employeeName = en;
  }

  greet() { // function
    console.log(`Hi ${this.employeeName}`)
  }
};

let emp1 = new Employee('Damon');
console.log("What is the instance variable value: ", emp1.employeeName); // Important concept
console.log(emp1.greet());

// Now we introduce 'super' usage for another class: (How inheritance works)
class Manager extends Employee {
  constructor(managerName: string) {
    super(managerName);
  }

  delegateNameForManager() {
    console.log('Manager class can call employee class method and assign its value emplyee class like below result: ');
  }
}

let m1 = new Manager('Ella');
m1.delegateNameForManager();
console.log('Employee class variable got value from Manager class: ', m1.employeeName);
m1.greet();

// Access Modifier
// public: (default one) enable to be access by any class or functions
// private: only can be accessed by its own class, not outside the class
// protected: can be access only when within the class and subclasses)

// Example below:

class A {
  protected name: string;
  constructor(_name: string) {
    this.name = _name;
  }
}

class B extends A {
  private department: string;
  constructor(whatever: string, department: string) {
    super(whatever);
    this.department = department;
  }

  public message() {
    return `Hi, my name is ${this.name} and I worked in ${this.department}.`;
  }
}

let p1 = new A('Damon');
console.log('whats p1? ', p1);
// console.log('whats p1? ', p1.name); // got error for accessing p1.name
let instanceOfB = new B('Damon', "IT");
console.log(instanceOfB.message());
// console.log(instanceOfB.name); // got error for accessing instanceOfB.name

2. Difference between interface and type:

Interface: describes the data shapes, eg: object (what the object going to look like)

can do declaration merging, type is not allowed

eg:

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

interface MergingInterface: BaseInterface {
  hobby: string[];
  boc: string;
}

Type: defines a type of data, eg: a primitive, a tuple or a union

//  type unique feature: allows developer to setup a type alias for just a single core type
type Age = number;

const myAge: Age = 30;

Reference

3. xxx.d.ts: its call typescript declaration file, which is the type definition files that allow to use JavaScript code in Typescript.

tsc --init // auto generate the tsconfig.json file

tsc example.ts --declaration // will generate 2 versions of ts file example.js and example.d.ts file

reference

4. A resource

5. typescript type checking playground here

6. How to fix property does not exist on type {} (Typescript). Reference

7. Any type

declare module '@private/npm-package'; can be imported globally

type Record<K extends keyof T> = {
  [P in K]: T;
};

type Any = Record;

Reference

8. TypeScript class vs interface:

// class way: good for class based definitions
interface Artist {
  name: string;
}

class ArtistCreator {
  constructor(public name: string) {}
}

function artistFactory({ name }: ArtistCreator) {
  return new ArtistCreator(name);
}

artistFactory({ name: 'damengrandom' });


// interface way: quick and normal definitions
interface Artist {
  name: string;
}

function artistFactory({ name }: Artist) {
  return {id: 1, name };
}

9. how to use typeof and keyof to define type in TypeScript:

const person = {
  name: 'damon',
  age: 33
};

type Person = typeof person;

type PersonKeys = keyof Person; // will return: name | age

type PersonTypes = Person[PersonKeys]; // will return: string | number

// using generic type to get sepcific type of the value from the key

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const personName = getProperty(person, 'name'); // now the type for personName is string
// const personNameWrongType = getProperty(person, 'names'); // now get the error of "Argument of type '"names"' is not assignable to parameter of type '"name" | "age"'.ts(2345)"

const anotherPerson: Person = {
  name: 1, // will get an error said: "Type 'number' is not assignable to type 'string'.ts(2322)",
  age: 33
}

const anotherPersonNow: PersonTypes = {
  name: 1, // will get an error said: "Type '{ name: number; age: number; }' is not assignable to type 'PersonTypes'.ts(2322)",
  age: 33
}

// "typeA | typeB" is called as union type in TypeScript

10. TypeScript Enum:

// in ts
enum Sizes {
  Small,
  Medium,
  Large,
}

console.log(Sizes.Medium);
console.log(Sizes[Sizes.Large]);

// after compiled to JS code:
"use strict";
var Sizes;

(function(Sizes) {
  Sizes[Sizes["Small"] = 0] = "Small";
  Sizes[Sizes["Medium"] = 1] = "Medium";
  Sizes[Sizes["Large"] = 2] = "Large";
})(Sizes || (Sizes = {}));

console.log(Sizes.Medium); // 1
console.log(Sizes[Sizes.Large]); // "Large"

Another example

const enum Sizes { // using the const to define a enum, the compile code will be an object rather than a function
  Small = 'small',
  Medium = 'medium',
  Large = 'large',
}

let selected: Sizes = Sizes.Small;

function updateSize(size: Sizes): void {
  selected = size;
}

updateSize(Sizes.Large);

console.log(selected); // large

// After code compiled from ts to js:
"use strict";
var selected = "Small" /* Small */;
function updateSize(size) {
  selected = size;
}
updateSize("large" /* Large */);
console.log(selected);

11. TypeScript intersection + union (discriminated union) type:

interface Order {
  id: string;
  amount: number;
  currency: string;
}

interface Stripe {
  type: "stripe"; // we fixed the value and type !! (discriminated)
  card: string;
  cvc: string;
}

interface Paypal {
  type: "paypal"; // we fixed the value and type !! (discriminated)
  email: string;
}

type CheckoutCard = Order & Stripe;
type CheckoutPaypal = Order & Paypal;

// inline intersection example
// type CombinedType = TypeOne & { attr: string };

// For the union type

type Payload = CheckoutCard | CheckoutPaypal; // payload is the union type

const order = {
  id: "1",
  amount: 1000,
  currency: "AUD",
};

const orderCard: CheckoutCard = {
  ...order,
  type: "stripe",
  card: "4242 4242 4242 4242",
  cvc: "123",
};

const orderPaypal: CheckoutPaypal = {
  ...order,
  type: "paypal",
  email: "test@test.test",
};

function checkout(payload: Payload) {
  if (payload.type === "stripe") {
    console.log(payload.card, payload.cvc);
  }
  if (payload.type === "paypal") {
    console.log(payload.email);
  }
}

12. How to use is in TypeScript:

// Song is a class
class Song {
  constructor(public title: string, public duration: number) {}
  // ...
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
  // ...
}

// function isSong(item: any): item is Song { // item is a Song typed object or item is instance of Song
//   return item instanceof Song;
// }

// another way to use `in` statement

function isSong(item: any): item is Song { // item is a Song typed object or item is instance of Song
  return 'title' in item;
}

function getItemName(item: Song | Playlist) {
  return isSong(item) ? item.title : item.name;
}

13. Typescript DefinitelyTyped and @types:

Normally when people talks about DefinitelyTyped, which is can be refers to *.d.ts files, which means when you install a npm package, you also need to install the type definition files as well, eg: yarn add @types/lodash

@types files will be automatically added to the global scope before it can be used for the *.ts files

types compiler options: eg: { "compilerOptions": { "types": ["lodash", "express"] } } add it inside tsconfig.json file !! above line means typescript will do type checking for lodash and express npm package

Some Cases:

Case 1: If package does not have declareation files, we can write our own declareation file:

Step 1: go to src folder and create a new folder called @types

Step 2: go to @types folder and create a new folder called YOU_NPM_PACKAGE_NAME, eg lodash

Step 3: go to YOU_NPM_PACKAGE_NAME (eg: lodash) folder and create a new file called index.d.ts

Step 4: go to index.d.ts file and start to define you the types now

// in the ts file, eg: app.ts
import * as _ from 'lodash';
_.chunk([1,2,3,4], 2); // [[1,2], [3,4]]

// in index.d.ts file,
declare module 'lodash' {
  export function chunk(collection: T, size?: number): T[][];
}
// Remember using `declare module` keywords for declare a package module, eg: `declare module 'lodash' { ... };`

14. Downside of using exlamation mark (!) in Typescript:

! operastor does not change the runtime behaviour your code.

For example: if the value you have asserted is not null or undefined, turned out actually to be null or undefined, an error will occur and disrupt the execution of your code

In Typescript, not recommanded to use ! operator at the end of variable !!!!

Reference

15. readonly note for TS

TS readonly, please check !!!!

Example:

interface IObject {
    readonly readOnlyProp: number;
    name:  string;
}

let obj: IObject = {
    readOnlyProp: 1,
    name: "Damon"
}

obj.readOnlyProp = 99; // Compiler Error: Cannot change readonly 'readOnlyProp'

16. TypeScript function overload:

function reverse(str: string): string; // this line will not be compiled to JS code (ts only)
function reverse<T>(arr: T[]): T[];  // this line will not be compiled to JS code (ts only)
function reverse<T>(stringOrArray: string | T[]): string | T[] {
  if (typeof stringOrArray === 'string') return stringOrArray.split('').reverse().join('');

  return stringOrArray.slice().reverse();
}

reverse('pepperoni');
reverse(['bacon', 'pepperoni', 'chili', 'mushrooms']);

17. TypeScript type Aliases:

interface Item {
  name: string;
}

interface Artist extends Item {
  songs: number;
}

interface Artist {
  getSongs(): number;
}

type Artist2 = {
  name: string,
} & Item;

//  REMEMBER: in typescript, we cannot have 2 same type defined, but we CAN have 2 same interface defined !!! [THIS IS THE DIFFERENCE BETWEEN interface and type]

const newArtist: Artist = {
  name: "damon",
  songs: 6,
  getSongs() {
    return this.songs;
  },
};

18. TypeScript generic:

class Pizza {
  constructor(private name: string, private price: number) {}
}

class List<T> {
  private list: T[];

  addItem(item: T): void {
    this.list.push(item);
  }

  getList(): T[] {
    return this.list;
  }
}

const list = new List<Pizza>();

list.addItem(new Pizza('pepperoni', 15));

const pizzas = list.getList();

class Coupon {
  constructor(private name: string) {}
}

const anotherList = new List<Coupon>();

anotherList.addItem(new Coupon('hi'));

19.