Skip to content

09. Typescript

Carlos Jaramillo edited this page Jun 5, 2021 · 24 revisions

Typescript

TypeScript es un lenguaje de programación de código abierto, desarrollado y mantenido por Microsoft. Es un superconjunto(superset) de JavaScript, que esencialmente añade tipos estáticos y objetos basados en clases. Se transpila a JS (fácilmente usando parcel-bundler).

JS es dinamicamente y debilmente tipado.

Tipos básicos

  • boolean: Valor verdadero o falso.
let muted: boolean = true;
  • number: Números.
let numerador: number = 42;
  • string: Cadenas de texto.
let saludo: string = `Me llamo ${nombre}`;
  • string[]: Arreglo del tipo cadena de texto.
let people: string[] = [];
  • Funciones :void, :never.
const noReturn = ():void => {};
const noFinish = ():never => {}; // not finish because is a infinity loop or throw exception.
  • Union :number | string.
let age: number | string;
age = 20;
age = '20';

age.toString(); // execute shared methods between both types
// Union type remove the security of the object type but can recover it with TypeGuards

// TYPE GUARDS
// to take advantage of union types without losing the security of type.

// the syntax of returned type conver this function in type guard
function isNumber(obj: number | string): obj is number { 
     return typeof obj == 'number';
}

function printAge(age: number | string) {
    if(isNmber(age)) {
        // security about the object is number;
    } else {
        // heree typescript know (infer) is a string
        age.charAt(0);
    }
}
  • Intersection Types :User & admin.
class User {
    name: string;
}

class Admin {
    permissions: number;
}

let user: User & Admin;

user.name = 'Carlos';

user.permissions = 5; 

user = new User(); // error, because `user` is `User&Admin`

// TYPE ASSERTIONS
// is different from casting, you do not change the object, just override the compiler handle.

user = new User() as User&dmin;

// TYPE ALIASES
type numero = number;
let edad: numero;

type NumberOrString = number | string;
let age: NumberOrString;
  • Tuples :[string, number]. The difference with arrays, is the array only will accept one type
let tupla: [string, number];

// Error, because want string at [0] and number at [1]
tupla[0] = 20;
tupla[1] = '20';

tupla[2] = '1'; // this will accept union `string | number`
  • Array: Arreglo multi-tipo, acepta cadenas de texto o números.
// mutable
let peopleAndNumbers: Array<string | number> = [];
let peopleAndNumbers: (string | number)[] = [];

// inmutable
let peopleAndNumbers: ReadonlyArray<string | number> = [1, 'a', 'b'];

// const assertion
const config = ['http://hocalhost', 8000, '/api'] as const;

const config: readonly = ['http://hocalhost', 8000, '/api']; // cada item es readonly y su tipo es su propio valor
  • enum: Es un tipo especial llamado enumeración.
enum Color {
  First = "Rojo",
  Second = "Verde",
  Third = "Amarillo",
}

enum PaymentState { Creado, Pagado, EnDeuda }; // will assign 1, 2, 3
 
let colorFavorito: Color = Color.First;
  • any: Cualquier tipo.
let comodin: any  = "Joker";
comodin = { type: "WildCard" }
  • unknown: Cualquier tipo, pero debes revisar el tipo antes de ejecutar cualquier método.
const stringify = (value: unknown): string => {
  if (value instanfeof Date) {
    return value.toISOString();
  }
  if (Array.isArray(value)) {
    return JSON.stringify(value);
  }
}
  • object: Del tipo objeto.
let someObject: object = { type: "WildCard" };

type Config = {
  url: strin,
  port: number,
  path: string,
};

const config: Config = {
  url: 'http://localhost',
  port: 8000,
  path: '/api'
}

Funciones

function add(a: number, b: number): number {
  return a + b;
}
const sum = add(4, 25)

function createAdder(a: number): (number) => number {
  return function (b: number) {
    return a + b;
  }
}

type Double = (x: number) => number;
interface Double = { (x: number): number }

const double: Double = (x) => x * 2;
const double2: Double = function (x) {
  return x * 2;
}

Interfaces

interface Rectangulo {
  height: number,
  width: number
}

let rect: Rectangulo = {
  height: 4,
  width: 3
}

Genericos

function genericReceptor<T>(obj: T): T {
  return obj;
};

genericReceptor<string>('Carlos');
genericReceptor<number>(20);


// can be used in classes and interfaces
class Printer<T> {
  printAll(arr: T[]) {
    console.log(rr.length);
  }
}

let printer: Printer<number> = new Printer();
printer.printAll([12, 15]);


interface Asset {
 x, y: number;
}
function generic<T extends Asset>(obj: T) {};

generic<number>(20); // ERROR

class Point {
  x: number;
  y: number;
}
generic<Point>(new Point());


interface Asset<T> {
 x, y: number;
 generic: T;
}
function generic<T extends Asset<string>>(obj: T) {};

class Point implements Asset<string> {
  x: number;
  y: number;
  generico: string;
}
generic<Point>(new Point());


class Elements implements Asset<Point> {
  x: number;
  y: number;
  generico: Point;
}


const appendToArr = <T, U> (
  value: T,
  arr: ReadonlyArray<U>,
): ReadonlyArray<T | U> => [...arr, value];

const arr = ['a', 1]; // const arr: (strung | number)[];
const arr2 = appendToArr(false, arr) // const arr2: (strung | number | boolean)[];

Clases

JavaScript tradicional utiliza funciones y herencia basada en prototipos

class Greeter {
    greeting: string; // propertie

    // not return, only can exist 1
    constructor(message: string) {
        this.greeting = message;
    }

    greet() { // method
        return"Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

Clases Abstractas

Similares a las interfaces, pero estas si pueden tener implementación y las interfaces no. No pueden ser instanciadas, pero pueden tener métodos abstractos, que no deberían tener implementación si no que deben ser implementados por las sub-clases, como los metodos de las interfaces.

abstract class Greeter {
    x: number;
    y: number;
    getCoords(): string { return `${this.x},${this.y}` }

    abstract greet(name: string): string;
}

// Can't extends more classes.
class Hero extends Greeter {
    // Doesn't need implement x,y, getCoords
    greet(name: string) { return  `Hello ${name}` }
}

Herencia

class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

/*
An alternative to this is implements Interface, 
some class only can extends one class/abstract class
but can implements multiples interfaces
*/
class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }

    /*
    // to override methods from parent
    move() {
      super.move(10); // to call methods from parent can use super.anyMethod();
    }
    */
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();

Modificadores de Acceso

Encapsulación: Ocultar implementación de la funcionalidad y sólo exponer una forma de mandar a llamar la implementación. No deberíamos tener atributos publicos. Ahí entran los métodos accesores (Getter/Setters).

Esto no es por seguridad, ** analogía ** no tienes que saber cómo funciona el motor, sólo necesitas la interfaz * volante, pedales * todo lo demás sucede internamente. Para sólo exponer la interfaz, la forma de usar el objeto y no sus detalles. Para crear objetos independientes más fáciles de re utilizar.

Public (default)

class Animal {
    static url: string = 'http://google.com';
    /* 
    To access this just need to use `Animal.url` no need instance objects to access this. 
    Is used when the info is from the class and no from the object. Only need 1 copy of this variable. 
    Will access to this variable from the class instead of object. can create static methods too, 
    and to call it just need use `Animal.method()` and is used when method does not need internal state.
    */

    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

Private

No se puede acceder desde afuera de su clase.

class Animal {
    private _name: string;
    constructor(theName: string) { this._name = theName; }
    
    // Métodos accesores
    get name() { return this._name; }
    set name(name: string) { this._name = name; }
}

new Animal("Cat")._name; // Error: 'name' is private
new Animal("Cat").name; // Método accesor

Protected

Similar a private, añadiendo que las clases derivadas también pueden accederlo.

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

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name); // every time you override constructor need call super();
        this.department = department;
    }

    public getElevatorPitch() {
        return`Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error

Namespaces

Para agrupar identificadores (clases, interfaces, constantes, simbolos) bajo un mismo nombre. Para evitar colisiones.

namespace MY {
    export class YouTube { };
    export const url: string = 'http://google.com';
    const private: string = '123';
}

let video: CF.YouTube = new CF.YouTube();

Decorators

Design pattern, where you get some property, class, or method, and you wrap it to add info or functionality. Want's to extend the functionality of some component without modifying it. To use with or without the decorator. in JS right now are in stage 2. But you can implement it with multiples strategies. Or with typescript because right now are experimentals, but angular use it a lot.

// get function who represents class constructor (is no the constructor function), 
// is the function who the object are createds. Because JS is prototype oriented and not classes. 
function auditClass(cls: Function) {
  cls.prototype.className = cls.name;
  console.log('decorator executed'); // is executed without instance the function
}

// clsProto: Hero or any, because usually decorators wants be generics.
function auditProperty(clsProto: any, propertyName: string) {
  clsProto.className = clsProto.constructor.name; // clsProto.constructor is the class
  console.log('decorator executed', propertyName);
}

function auditStaticProperty(cls: Function, propertyName: string) {
  cls.prototype.className = cls.name;
  console.log('decorator executed', propertyName);
}

// the descriptor is optional, because only get in target es5+ and will come with this props: https://www.javascripture.com/PropertyDescriptor
function auditMethod(clsProto: any, methodName: string, descriptor?: any) {
  let originalFunction = clsProto[methodName]; // property value or method decorated.

  let decoratedFunction = function() {
    originalFunction();
    console.log(`function ${methodName} executed`)
  }

  descriptor.value = decoratedFunction;
  return descriptor;
}

function auditMethodParam (clsProto: any, methodName: string, paramPosition: number) {
  clsProto.className = clsProto.constructor.name; // clsProto.constructor is the class
  console.log('decorator executed', methodName, paramPosition);
}

// decorator factory // all decorators can implement factory to get arguments
function Auditable (message: string) {

  return function auditMethod(clsProto: any, methodName: string, descriptor?: any) {
    let originalFunction = clsProto[methodName];

    let decoratedFunction = function() {
      originalFunction();
      console.log(`message`)
    }

    descriptor.value = decoratedFunction;
    return descriptor;
  }
}

@auditClass
class Hero() {
  // all decorators can be used on top or before some component.
  @auditProperty title: string;

  @auditStaticProperty 
  static site: string;

  @auditMethod
  greet(@auditMethodParam example: string) { console.log('Hi`) }

  @Auditable('Tracking')
  thanks() { console.log('Thanks`) }
}

let hero: Hero = new Hero();
console.log(hero.className); // ts error - doesn't exist because decorators works in execution time
console.log((hero as any).className);
hero.greet();
hero.thanks();

tsconfig.json

before es6 need use a specific module handler

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "removeComments": true,
    "outDir": "dist",
    "noImplicitAny": true,
    "sourceMap": true,
    "watch": true,
    "rootDir": "src",
    "experimentalDecorators": "true"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

Clone this wiki locally