Skip to content

Latest commit

 

History

History
770 lines (528 loc) · 22.2 KB

File metadata and controls

770 lines (528 loc) · 22.2 KB
sidebar_position 1

💎 TypeScript

Qué es TypeScript?

Es un superset de JavaScript, es el mismo JavaScript pero le extiende características, o posee cosas propias.

Es mantenido por Microsoft

  • Ofrece tipado estricto y flexible
  • Mejora la legibilidad del código
  • Nos permite usar características modernas de JavaScript sin miedo a la compatibilidad
  • Mejora el intellisense
  • Nos permite la inyección de dependencias

Su tipado estricto ayuda a saber cómo funcionan las cosas; por ejemplo, en este código JavaScript:

function calcularISV (productos) {
 let total = 0;

 productos.forEach( ({precio}) => {
  total += precio;
 }

 return [total, total * 0.15]
}
  • No sabemos de que tipo de dato son los productos
  • Como luce un producto
  • Que propiedades debe tener el producto para que el código funcione

Entonces, en TypeScript quedaría así la firma de nuestro método.

function calcularISV(productos: Producto[]): [number, number] {}
  • Productos es un arreglo del tipo Producto
  • ¿Cómo luce Producto? Se puede definir con una interfaz
interface Producto {
 desc: string;
 precio: number;
}
  • Retorna un arreglo de dos números

TSConfig.json

Es el archivo de configuración de TypeScript, en el se definen las reglas de compilación de TypeScript.

Mediante estas configuraciones se debería buscar que TypeScript sea lo más estricto posible.

{
 "compilerOptions": {
  "target": "es5", // A que versión de JS se compilará
  "module": "commonjs", // Sistema de módulos
  "strict": true, // Habilita todas las opciones estrictas
  "noImplicitAny": true, // No permite any
  "esModuleInterop": true, // Permite importar módulos de ES6
  "skipLibCheck": true, // No revisa las librerías
  "forceConsistentCasingInFileNames": true, // Obliga a que los nombres de archivos sean consistentes
 }
}

Watch Mode

TypeScript, al ejecutarse, es traducido a JavaScript. Para que TypeScript esté pendiente de los cambios en los archivos y los compile automáticamente, se puede usar el siguiente comando.

tsc --watch

Variables

Por defecto, implícitamente, TypeScript asigna un tipo a las variables, pero se puede asignar un tipo explícito. any debería evitarse a toda costa, a menos que sea un elemento reutilizable.

let nombre: string = 'Denu'; // Tipo explicito
let nombre= 'Denu' // Tipo implicito

// Variable de dos posibles tipos
let variable: number | string = 95;
variable = 'Denu'

let arrayVariable: (boolean | string | number)[] = ['Denu', 22, false]

Objetos

interface Personaje {
 nombre: string;
 edad: number;
 habilidades: string[];
 nacionalidad?: string; // opcional
}

const personaje: Personaje = {
 nombre: 'Denu',
 edad: 24,
 habilidades: ['volar', 'comer']
}

personaje.nacionalidad = 'Costa Rica';

Tuples - Tuplas

Una tupla en TypeScript es un tipo especial de arreglo donde se conoce el tipo de dato y la cantidad de elementos que contiene. Permite definir una estructura de datos con un número fijo de elementos, cada uno con su propio tipo.

Por ejemplo:

let tupla: [string, number];
tupla = ['Denu', 22]; // Correcto
// tupla = [22, 'Denu']; // Error: el orden y tipo deben coincidir

Las tuplas son útiles cuando se necesita retornar o manejar un conjunto de valores relacionados pero de diferentes tipos.

También pueden tener elementos opcionales o usar el operador rest:

let tuplaOpcional: [string, number?] = ['Denu'];
let tuplaRest: [string, ...number[]] = ['Denu', 1, 2, 3];


Never

Es un tipo de dato que representa un valor que nunca ocurre. Se utiliza principalmente para funciones que lanzan excepciones o que nunca terminan de ejecutarse.

No es undefined ni es void. Es never.

function error(mensaje: string): never {
 throw new Error(mensaje);
}

function loopInfinito(): never {
 while (true) {
  console.log('Hola');
 }
}

Suele aparecer bastante cuando declaro un Array pero nunca declaro su tipo, y luego intento insertar elementos dentro de la misma

let arreglo = [];
arreglo.push(1); // Error - Array no acepta number dentro de su tipo never[]

Any

Es un tipo de dato que puede ser cualquier cosa, es decir, que no tiene un tipo de dato específico. Se debe evitar su uso en la medida de lo posible, ya que puede llevar a errores en tiempo de ejecución y dificultar el mantenimiento del código.

let variable: any = 'Denu';
variable = 22;
variable = true;

En Javascript se traspilaria a

let variable = 'Denu';
variable = 22;
variable = true;

Null vs undefined vs never

null y undefined son valores que existen tanto en JS como en TS, pero never es propio de TS.

null es ningun valor en particular, califica como inicializacion en si mismo. undefined es el valor por defecto de una variable que no ha sido inicializada. never es un tipo que representa un valor que nunca sucedera. Aparece seguido en errores de compilacion cuando se declara un array pero, o no se inicializa o no se declara de que tipo es, y se intenta hacer alguna operacion con la misma.

let a: null = null;
let b: undefined = undefined;

let c: never = (() => {
  throw new Error("Error");
})();

any vs unknown vs never

  • any : desactiva el chequeo. Podés asignar/callar/operar sin seguridad.
// any: podés hacer cualquier cosa
let a: any = 5;
a = "hola";
a.noExiste(); // no error
  • unknown : acepta cualquier valor, pero no te deja usarlo sin validar (type guard).
// unknown: tenés que validar
let u: unknown = "hola";
// u.toUpperCase(); // error

if (typeof u === "string") {
  u.toUpperCase(); // OK
}
  • never : representa “nunca ocurre”. Se usa en funciones que no retornan o en ramas imposibles.
// never: nunca ocurre
function crash(): never {
  throw new Error("boom");
}

Generics

Los generics son una característica de TypeScript que permite crear componentes, funciones y clases que pueden trabajar con una variedad de tipos de datos. Los generics permiten escribir código que es reutilizable y flexible, ya que no está limitado a un solo tipo de datos.

Para definir un tipo genérico en TypeScript, se utiliza la sintaxis de ángulos (<>) para especificar un tipo de parámetro genérico. Por ejemplo, la siguiente función toma un argumento de tipo genérico T y devuelve un array de ese tipo:

function makeArray<T>(value: T): T[] {
    return [value];
}

const array1 = makeArray<number>(1); // array1 es de tipo number[]
const array2 = makeArray<string>('hello'); // array2 es de tipo string[]

Es ideal para reemplazar el uso de any. Por ejemplo, si se usa any, se pierde la información del tipo de datos que se está utilizando, lo que puede llevar a errores en tiempo de ejecución. Con generics, se puede especificar el tipo de datos que se espera y TypeScript se encargará de verificar que se cumpla en tiempo de compilación.


Cómo se transpilan las interfaces de TypeScript?

Las interfaces en TypeScript se no transpilan a código JavaScript, ya que son un mecanismo exclusivo del tiempo de compilación para mantener el tipado estático y ayudar en la detección de errores durante el desarrollo. En otras palabras, las interfaces solo existen en TypeScript mientras se escribe y compila el código, pero al momento de la transpilación final a JavaScript, se eliminan completamente porque JavaScript no reconoce ese concepto.

  • Las interfaces definen "contratos" o "tipos" para objetos, clases o funciones en TypeScript, especificando qué propiedades y métodos deben existir y qué tipos tienen.
  • Cuando se transpila TypeScript a JavaScript, todo lo relacionado con las interfaces se elimina, porque JavaScript no tiene soporte nativo para interfaces o tipos estáticos.
  • Solo el código "real" y ejecutable (funciones, clases, instrucciones) se convierte en JavaScript; las interfaces se usan solo para la validación en tiempo de compilación.
  • Por ejemplo, si defines una interfaz para tipar un objeto, al generar el código JavaScript, no aparecerá ninguna referencia a esa interfaz ni ninguno de sus detalles.

Las interfaces TypeScript no se transpilan a JavaScript; son eliminadas durante la compilación, ya que solo sirven para verificar tipos en tiempo de desarrollo, sin generar código runtime.

Esto permite que el código resultante sea código JavaScript limpio y compatible con cualquier entorno, sin cargas extras por el sistema de tipos de TypeScript.


Cómo hago que las props de una interface sean opcionales?

Para hacer todas las propiedades de una interfaz opcionales en TypeScript, puedes utilizar la característica de TypeScript llamada "intersección" junto con el tipo parcial ("Partial").

interface MyInterface {
  prop1: string;
  prop2: number;
}

// Todas las propiedades de MyInterface ahora son opcionales
type PartialMyInterface = Partial<MyInterface>;

// Ejemplo de uso
const obj: PartialMyInterface = {}; // Todas las propiedades son opcionales

En este ejemplo, Partial<MyInterface> crea un nuevo tipo que tiene todas las propiedades de MyInterface, pero las declara como opcionales. Esto permite que cada propiedad pueda ser undefined o simplemente omitida al crear un objeto que cumpla con este tipo.

Otra opcion seria agregando un ? al lado de la propiedad, de esta forma no sera obligatoria su existencia, aunque puede que se necesiten checks extras en el lado del codigo para verificar su no-nulidad

interface MyInterface {
  prop1?: string;
  prop2?: number;
}

Union e Intersection

Union Types:

Los tipos de unión (Union Types) en TypeScript permiten definir un tipo que puede ser uno de varios tipos diferentes. Se utilizan para representar valores que pueden ser de diferentes tipos en un contexto determinado. Por ejemplo, un tipo de unión se define utilizando el operador de unión | entre los tipos que se permiten.

type Status = 'active' | 'inactive';
type ID = number | string;

Intersection Types:

Los tipos de intersección (Intersection Types) en TypeScript permiten combinar varios tipos en uno solo. Se utilizan para crear un nuevo tipo que tiene todas las propiedades y métodos de los tipos combinados. Por ejemplo, un tipo de intersección se define utilizando el operador de intersección & entre los tipos que se desean combinar.

type User = { name: string; age: number };
type Admin = User & { role: 'admin' };
  • Union Types: Se utilizan cuando un valor puede ser de varios tipos diferentes en un contexto determinado. Por ejemplo, cuando un valor puede ser una cadena o un número, se puede utilizar un tipo de unión para representar esta posibilidad.

  • Intersection Types: Se utilizan cuando se desea combinar varios tipos en uno solo para crear un nuevo tipo que tenga todas las propiedades y métodos de los tipos combinados. Por ejemplo, cuando se desea crear un nuevo tipo que tenga todas las propiedades de dos tipos existentes, se puede utilizar un tipo de intersección para lograr esto.


Partial, Pick, y Omit

Los Utility Types en TypeScript son tipos predefinidos que permiten realizar transformaciones y operaciones comunes en tipos de datos existentes. Estos tipos proporcionan funcionalidades útiles para trabajar con tipos de datos de una manera más flexible y reutilizable. Algunos de los Utility Types más comunes en TypeScript son Partial, Pick, y Omit.

Partial:

El tipo Partial en TypeScript se utiliza para hacer todas las propiedades de un tipo dado opcionales. Esto significa que cada propiedad del tipo se convierte en opcional, lo que permite crear objetos que no requieren todas las propiedades del tipo original.

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

type PartialPerson = Partial<Person>;

const partialPerson: PartialPerson = {}; // Todas las propiedades son opcionales

Pick:

El tipo Pick en TypeScript se utiliza para seleccionar un subconjunto de propiedades de un tipo dado y crear un nuevo tipo que contenga solo esas propiedades seleccionadas. Esto permite crear tipos personalizados que contienen solo las propiedades necesarias de un tipo existente.

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

type PersonName = Pick<Person, 'name'>;

const personName: PersonName = { name: 'Alice' }; // Solo contiene la propiedad 'name'

Omit:

El tipo Omit en TypeScript se utiliza para crear un nuevo tipo que excluye una o más propiedades de un tipo dado. Esto permite crear tipos personalizados que contienen todas las propiedades de un tipo existente, excepto las propiedades especificadas.

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

type PersonWithoutAddress = Omit<Person, 'address'>;

const personWithoutAddress: PersonWithoutAddress = { name: 'Alice', age: 30 }; // No contiene la propiedad 'address'

Decoradores

Para usar decoradores, debes habilitar la opción experimentalDecorators en el archivo tsconfig.json:

{
 "compilerOptions": {
  "experimentalDecorators": true
 }
}

Tipos de decoradores en TypeScript:

  • Decoradores de Clase

Se aplican a la definición de una clase. Reciben como argumento el constructor de la clase y pueden modificar la clase misma.

function Logger(target: Function) {
  console.log(`Clase registrada: ${target.name}`);
}

@Logger
class User {
  constructor(public name: string) {}
}

// Output: "Clase registrada: User"
  • Decoradores de Método

Se aplican a los métodos de una clase y reciben metadatos sobre el método. Son útiles para extender o modificar el comportamiento de los métodos.

function LogMethod(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Método ${methodName} ejecutado con argumentos: ${args}`);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @LogMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// Output:
// "Método add ejecutado con argumentos: 2,3"
// 5
  • Decoradores de Propiedad

Se aplican a las propiedades de una clase. Reciben el prototipo del objeto y el nombre de la propiedad. Los decoradores de propiedad no tienen acceso al valor de la propiedad directamente (ya que el valor no está inicializado en tiempo de compilación).

function PropertyMetadata(target: any, propertyName: string) {
  console.log(`Propiedad decorada: ${propertyName}`);
}

class Product {
  @PropertyMetadata
  price: number;
}

// Output: "Propiedad decorada: price"
  • Decoradores de Parámetro

Se aplican a los parámetros de un método y reciben información sobre el parámetro decorado. Son útiles para implementar patrones como inyección de dependencias.

function LogParameter(target: any, methodName: string, parameterIndex: number) {
  console.log(`Parámetro decorado en el método ${methodName}, índice: ${parameterIndex}`);
}

class UserService {
  greet(@LogParameter name: string) {
    console.log(`Hola, ${name}!`);
  }
}

const userService = new UserService();
userService.greet("Ana");
// Output:
// "Parámetro decorado en el método greet, índice: 0"
// "Hola, Ana!"
  • Decoradores de Getter/Setter

Se aplican a los accesores (get y set) y funcionan como los decoradores de método, pero se usan para modificar el comportamiento de getters y setters.

function LogAccessor(target: any, propertyName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.get;
  descriptor.get = function () {
    console.log(`Accediendo a la propiedad ${propertyName}`);
    return originalMethod?.apply(this);
  };
}

class User {
  private _age: number = 30;

  @LogAccessor
  get age() {
    return this._age;
  }
}

const user = new User();
console.log(user.age);
// Output:
// "Accediendo a la propiedad age"
// 30

Los decoradores son muy usados en, por ejemplo, Angular, para configurar clases de cierta forma predeterminada

@Injectable()
class UserService {
  // Servicio disponible para otros
}

Conditional Types

Los Conditional Types en TypeScript son una característica avanzada que permite definir tipos condicionales basados en una condición booleana. Esto permite crear tipos que se comportan de manera diferente según una condición dada. Los Conditional Types se definen utilizando la palabra clave extends y la sintaxis de los tipos condicionales.

type IsString<T> = T extends string ? 'yes' : 'no';

type Test1 = IsString<string>; // 'yes'
type Test2 = IsString<number>; // 'no'

Parametros REST

Una funcion puede recibir tantos argumentos como uno quiera, y los parametros REST son una manera de hacer esto.

Por ejemplo en la proxima funcion puedo enviar tantos numeros como yo quiera sumar. Guarda los elementos en el array numeros

function sumar(a: number, b: number, ...numeros: number[]): number {
 return numeros.reduce((acc, curr) => acc + curr, 0);
}

sumar(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55

Clases

class Person {
 private age: number;
 apellido: string; // por defecto es public

  constructor(age: number, apellido: string) {
   this.age = age;
   this.apellido = apellido;
  }
}

const denu = new Person(24, 'Lemon');

// ---- Forma resumida ----

class Person {
 constructor(private age: number, public apellido: string) {}
}

Herencia

La herencia en TypeScript se logra mediante la palabra clave extends. Una clase puede heredar propiedades y métodos de otra clase utilizando la herencia. Aquí tienes un ejemplo de cómo se ve la herencia en TypeScript:

class Beer extends Drink {
 private alcohol: number;

 constructor(name: string, price: number, alcohol: number) {
  super(name, price);
  this.alcohol = alcohol;
 }
}

const beer = new Beer('Imperial', 1000, 5);

Interfaces

Nos permite categorizar objetos, es decir, que un objeto tenga un comportamiento ya esperado. Es un contrato.

interface Product {
 name: string;
 price: number;
}

const beer: Product = {
 name: 'Imperial',
 price: 1000
}

class Beer extends Drink implements Product {
 private alcohol: number;

 constructor(name: string, price: number, alcohol: number) {
  super(name, price);
  this.alcohol = alcohol;
 }
}

Types vs Interfaces

Ambos se utilizan para definir tipos en TypeScript, pero tienen algunas diferencias clave en su comportamiento y uso:

Interfaces

  • Extensibilidad: Las interfaces pueden extender otras interfaces, lo que permite la creación de jerarquías de interfaces y la reutilización de definiciones de tipo.
interface Animal {
 name: string;
}

interface Dog extends Animal {
 breed: string;
}
  • Declaración de Objetos: Las interfaces son ideales para definir la forma de un objeto y sus propiedades, métodos y tipos de datos esperados.
interface Person {
 name: string;
 age: number;
}

const person: Person = { name: 'Alice', age: 30 };
  • Compatibilidad: Las interfaces son más adecuadas para definir contratos y acuerdos entre diferentes partes del código, ya que representan un contrato que una clase o un objeto debe cumplir.
  • Merging: Las interfaces se pueden fusionar si tienen el mismo nombre, lo que permite agregar propiedades y métodos a una interfaz existente en diferentes archivos.
interface Person {
 name: string;
}

interface Person {
 age: number;
}

const person: Person = { name: 'Alice', age: 30 };

Types:

  • Unión y Intersección: Los tipos permiten la creación de tipos de datos más complejos mediante la unión y la intersección de tipos.
type Status = 'active' | 'inactive';
type User = { name: string; age: number };

type Admin = User & { role: 'admin' };
  • Tipos Primitivos: Los tipos pueden definir tipos primitivos, literales y tipos de datos más simples que las interfaces.
type ID = number;
type Status = 'active' | 'inactive';
  • Alias: Los tipos permiten la creación de alias de tipos, lo que facilita la reutilización de definiciones de tipo y la creación de tipos personalizados.
  • Compatibilidad: Los tipos son más adecuados para definir tipos de datos más simples y para realizar transformaciones y operaciones en tipos de datos existentes.

Mapped Types

Mapped Types en TypeScript son una forma de transformar tipos existentes en nuevos tipos utilizando un mecanismo de mapeo. Este es un concepto muy poderoso que te permite crear tipos dinámicamente basados en otros. Los Mapped Types se definen utilizando la sintaxis de los tipos indexados y la palabra clave in.

Un ejemplo común de Mapped Types es la creación de un tipo que convierte todas las propiedades de un tipo dado en propiedades opcionales. Esto se puede lograr utilizando un Mapped Type con la palabra clave Partial.

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

// Mapped Type que convierte todas las propiedades de Person en opcionales
type PartialPerson = {
 [K in keyof Person]?: Person[K];
};

const partialPerson: PartialPerson = {}; // Todas las propiedades son opcionales

En proyectos complejos, los Mapped Types pueden ser muy útiles para crear tipos dinámicamente basados en otros tipos existentes. Por ejemplo, puedes utilizar Mapped Types para crear tipos que transforman propiedades


Typeguards (typeof e instanceof)

Un type guard (o guardián de tipo) es una forma de decirle a TypeScript:

“Tranquilo, yo sé qué tipo de dato tengo acá”.

Sirve para que TypeScript entienda qué tipo exacto tiene una variable en un momento dado, especialmente cuando puede ser más de uno (por ejemplo, string | number)

// Con typeof

function imprimirDoble(valor: string | number) {
  if (typeof valor === "number") {
    console.log(valor * 2);
  } else {
    console.log(valor.repeat(2));
  }
}

// Con instanceof

class Perro { ladrar() {} }
class Gato { maullar() {} }

function hacerSonido(animal: Perro | Gato) {
  if (animal instanceof Perro) {
    animal.ladrar(); // 🐶
  } else {
    animal.maullar(); // 🐱
  }
}