Instalación
Global: npm install -g typescript
, nos permitirá usarlo en cualquier proyecto, en nuestra maquina local. Local: npm install typescript --save-dev
, no depende de que la maquina local del desarrollador tenga ts instalado, se instalara con todas las demás dependencias de desarrollo (recomendada).
Para verificar la version instalada
Global: tsc -v
Local: npm ls typescript
(o mirando el package.json
)
Configuración inicial
tsc -init
creara el archivo tsconfig.json
, donde definiremos el comportamiento que queremos de nuestro compilador de .ts.
Pondremos especial atención en las siguientes configuraciones:
"target":"ES2016"
: nos permite especificar la version
"rootdir":"./src"
: indica donde estarán los archivos de origen de nuestra aplicación
"outDir":"./dist"
: en que carpeta sera generado el archivo .js
"noEmitOnError":false
: hara que falle la compilación al encontrar errores
"removeComments":true
: removerá los comentarios en los archivos de salida
"sourceMap":true
: genera el mapeo de los archivos .ts
a .js
Con la configuración anterior, solo bastara con ejecutar tsc
en la linea de comando para que typescript ejecute el compilador usando las configuraciones definidas en tsconfig.json
Tipos
JS nativos
number
string
boolean
null
undefined
object
function
TS
any
: no se recomienda usarlo, solo para proyectos que se estan migrando a TSunknown
: Tipo desconocidonever
arrays
tuplas
Enums
Numbers, strings y boolean
/* inicializadas, TS infiere el tipo de dato y es opcional indicarlo */
let monto = 340_000
let nombre = "Juan"
let pagado = false
/* no inicializadas, es necesario indicar tipo */
let monto: number;
let nombre: string
/* o puede indicarse el tipo y ademas inicializarlas */
let monto: number = 340_000
let nombre: string = "Juan"
let pagado: boolean = false
function pagar(monto: number){
}
Arrays
/* Arrays */
let calificaciones: number[] = [45,67,99]
let calificaciones: Array<number> = [45,67,99]
let zones: string[] = ["london","new york","barcelona"]
let zones: Array<string> = ["london","new york","barcelona"]
/* Array mixtos */
let mixto: (number | string | boolean | Object) = ["hola",333, true]
Tuplas
Similares a los array, pero con longitud fija y con tipos de datos definidos.
/* tuplas */
let dato1:[number,string] = [1, "london"]
let dato2:[number,string[]] = [1, ["london","cali"]]
/* Al tener estructura fija, facilita la destructuración */
const [id, city] = dato1
Unknow
A diferencia de any
usar unknow
nos obliga a verificar el tipo de variable antes de operar con la variable de tipo unknow
let unknowVar: unknow;
unknowVar = true
unknowVar = 444
unknowVar = "hola"
// No nos permite hacer esto
unknowVar.toUpperCase()
// Nos obliga a hacer esto
if(typeof unknowVar === "string){
unknowVar.toUpperCase()
}
Never type
Lo usamos para indicar que una función nunca va a retornar algo, su ejecución no termina, un ejemplo practico es cuando se levanta una excepción del tipo throw
, que detiene la ejecución a causa de un error.
const fail = (message: string): never =>{
throw new Error(message)
}
Enums
/* enums : básicamente son diccionarios */
/* Ejemplo 1 */
enum Talla {
Chica = "s",
Mediana = "m",
Grande = "l",
ExtraGrande = "xl" }
const variable = Talla.Chica // "s"
const enum LoadingState { Idle, Loading, Success, Error}
const estado = LoadingState.Success // 2
/* Ejemplo 2 */
enum Roles {
admin: "administrator",
seller: "seller",
customer: "customer",
}
type User = {
userName: string
role: Roles
}
const newUser: User = {
userName: "Jhon",
role: Roles.admin // "administrator"
}
Union types
/* puede ser un string o number */
let userId: string | number
userId: 666
userId: "BG767"
function greeting(myText: string | number){
// your code
}
Objetos
/* Objetos */
const reserva: {
id: number,
nombre?: string, //? indica que el atributo es opcional
readonly value: number // atributo de sólo lectura
talla: Talla // tipo enums
} = {
id: 1,
nombre: "p34",
value: 800_000
talla: Talla.Chica
}
Tipos personalizados
/* tipos personalizados */
type Reserva = {
id: number,
nombre?: string, //? indica que el atributo es opcional
readonly value: number // atributo de sólo lectura
talla: Talla // tipo enums
}
const reserva: Reserva = {
id: 1,
nombre: "p34",
value: 800_000
talla: Talla.Chica
}
/* Array de tipos personalizados */
const reservas: Reserva[]=[]
/* Tambien lo podemos usar con union type */
type UserId = string | number
Literal types
/* Permitira solo los string que se le indiquen */
let shirtSize: "s" | "m" | "l" | "xl"
let shirtSize = "s"
/* lo recomendable seria definirlo como tipo personalizado */
type Sizes = "s" | "m" | "l" | "xl"
let shirtSize: Size
let shirtSize = "s"
Null y undefined
/* TS reconocera estas variables como Any */
let myNull = null
let myUndefined = undefined
/* la forma correcta es esta */
let myNull: null = null
let myUndefined: undefined = undefined
/* Podemos tener variables que inicialmente son null y luego se cargan datos */
let data: [number] | null;
Objetos anidados
type Direccion = {
numero: number,
calle: string,
pais: string
}
type Reserva = {
id: number,
nombre?: string, //? indica que el atributo es opcional
readonly value: number, // atributo de sólo lectura
talla: Talla, // tipo enums
direccion: Direccion
}
const reserva: Reserva = {
id: 1,
nombre: "p34",
value: 800_000,
talla: Talla.Chica,
direccion: {
numero: 687,
calle: "av 5th",
pais: "egypt"
}
}
Funciones
type Sizes = "s" | "m" | "l" | "xl"
const createProduct = (
title: string,
createdAt: Date,
stock: number,
size?: Sizes // usamos el ? para los parametros que son opcionales
){
return {
title,
createdAt,
stock,
size,
}
}
const product1 = createProduct("t-shirt", new Date(), 6, "xl")
const product1 = createProduct("t-shirt", new Date(), 6)
/* Indicando que la funcion va a retornar un number */
const calcTotal = (proces: number[]): number => {
let total = 0
prices.forEach(item=> total += item)
return total
}
/* Indicando que la funcion NO va a retornar valores */
const calcTotal = (proces: number[]): void => {
let total = 0
prices.forEach(item=> total += item)
console.log(total)
}
/* Recibiendo un objeto tipado como parametro*/
/* Ejemplo 1 */
const login = (data:{ email: string, password: number })=>{
// your code
}
login({
email: "user@email.com",
password: "123",
})
/* Ejemplo 2 */
const addProduct = (
data: {
title: string,
createdAt: Sate,
stock: number,
size?: Sizes,
}) =>{
// Your code
}
/* Ejemplo 3 : Usando un typer personalizado */
type Product = {
title: string,
createdAt: Sate,
stock: number,
size?: Sizes,
}
const addProduct = (data: Product) =>{
// Your code
}
Módulos: import y exports
// product.model.ts
export type Sizes = "s" | "m" | "l" | "xl"
export type Product = {
title: string,
createdAt: Sate,
stock: number,
size?: Sizes,
}
// product.service.ts
import { Product } from "./product.model"
export const addProduct = (data: Product) =>{
// Your code
}
export const calcStock = (data: Product): number =>{
// Your code
}
// main.ts
import { addProduct, calcStock } from "./product.service"
addProduct({
title: "laptop",
createdAt: new Date(),
stock: 25,
size?: "s",
})
nullish-coalescing
export const createProduct = (
id: string | number.
isNew: boolean,
stock?: number,
) => {
return {
id,
stock: stock || 10,
isNew: isNew || true,
}
}
// ¿Que ocurre cuando is new es false?
// va a retornar siempre true dado que
// false === false
// ¿Que ocurre cuando stock sea 0?
// va a retornar siempre 10 dado que
// 0 === false
// Para resolver este problema llega nullish-coalescing
const a = 0
const b = a ?? 10 // 0
const a = false
const b = a ?? true // false
// el operado ?? sólo retorna lo del lado derecho cuando
// cuando el valor es null or undefined
Valores predefinidos
export const createProduct = (
id: string | number.
isNew: boolean = true,
stock?: number = 10,
) => {
return {
id,
stock: stock
isNew: isNew
}
}
Sobrecarga de funciones
Sobrecargar o Overloading es la capacidad que nos permiten algunos lenguajes de programación orientados a objetos definir una función con declaraciones diferentes que se ejecutan en el mismo contexto.
Veamos un ejemplo:
Sin sobrecargar las funciones
// Si recibe un string retorna un array
// Si recibe un array de strings retorna un string
function parseStr(input: string | string[]): string | string[] {
if (Array.isArray(input)) {
return input.join(''); // string
} else {
return input.split(''); // string[]
}
}
const rtaArray = parseStr('Nico');
// Al implementar la función que retornara un array, para poder usar
// métodos de array, TS nos obligara a validar que es un array
if (Array.isArray(rtaArray)) {
rtaArray.reverse();
}
const rtaStr = parseStr(['N','i','c','o']);
// Al implementar la función que retornara un string, para poder usar
// métodos de string, TS nos obligara a validar que es un string
if (typeof rtaStr === 'string') {
rtaStr.toLowerCase();
}
Sobrecargando las funciones
// Definimos cada implementación
export function parseStr(input: string): string[];
export function parseStr(input: string[]): string;
// Luego definimos la implementación
export function parseStr(input: string | string[]): string | string[] {
if (Array.isArray(input)) {
return input.join(''); // string
} else if (typeof input === 'string'){
return input.split(''); // string[]
}
}
// De esta forma TS podra saber que tipo de dato tendremos en cada
// implementación sin tener que validar explicitamente
const rtaArray = parseStr('Nico');
rtaArray.reverse();
const rtaStr = parseStr(['N','i','c','o']);
rtaStr.toLowerCase();
Interfaces
Similares a los
types
pero con la capacidad de usar herencia
De esta forma con ayuda de Interface
podemos generar modelos base con atributos y mediante el uso de un extend
podemos heredar clases.
// base.model.ts
export interface BaseModel {
id: string;
createdAt: Date;
updatedAt: Date;
}
// product.model.ts
import { BaseModel } from './../base.model';
export interface Product extends BaseModel {
title: string;
stock: number;
}
// order.model.ts
import { BaseModel } from './../base.model';
export interface Order extends BaseModel {
products: Product[];
user: User;
}
Propiedades de solo lectura
usar readonly
nos ayudara a prevenir que sobre escribamos atributos que no queremos que sean modificados, de esta forma TS nos avisara cuando intentemos modificar estos atributos marcados como readonly
// base.model.ts
export interface BaseModel {
readonly id: string;
createdAt: Date;
updatedAt: Date;
}
Utility Types
TypeScript proporciona varios tipos de utilidades para facilitar las transformaciones de tipos comunes. Estas utilidades están disponibles a nivel global. (ver mas)
Omit, Partial
Omit
: nos permite omitir los atributos que indicamos, su opuesto es Pick
Partial
: nos permite crear objetos indicando solo alguno de sus atributos, su opuesto es Required
// product.dto.ts
import { Product } from './product.model';
// Omit Omitir id, createdAt y updatedAt
export interface CreateProductDto extends Omit<Product, 'id' | 'createdAt' | 'updatedAt'>{
categoryId: string;
}
// Partial nos permite recibir un subconjunto del objeto original
// (pone todos los atributos como opcionales)
export interface UpdateProductDto extends Partial<CreateProductDto> {}
// Note que el update extiende desde el CreateProductDto para no
// permitir la modificacion del id, createdAt y updatedAt
readonly
readonly
: definirá todos los atributos del objeto como solo lectura
Un caso de uso frecuente es cuando queremos hacer una búsqueda, y creamos un objeto con los atributos que deben hacer match pero queremos asegurarnos de que estos no sean modificados durante la ejecución, usaremos readonly
en conjunto con Partial
(todos opcionales) dado que para una búsqueda podemos enviar 1 o mas atributos.
// product.model.ts
// ...
export interface FindProductDto extends Readonly<Partial<Product>> {}
Acceder al tipado por indice
En determinados casos queremos definir un tipo basado en el tipado de otra propiedad, en estos casos podemos apoyarnos del tipado por indices.
// El id del updateProduct debe ser el mismo que el del Product
export const updateProduct = (id: string) {
//
}
// En caso que cambie el tipo del id del Product, tocaria cambiarlo ac
// también, para evitarnos eso, podemos usar tipado por indice
export const updateProduct = (id: Product["id"]) {
//
}
ReadonlyArray
Evitar las mutaciones en los arrays.
const numbers: ReadonlyArray<number> = [1,2,2,2];
// no podremos usar estos métodos
numbers.push(9);
numbers.pop();
numbers.unshift(1);
// SI podremos usar estos métodos
numbers.filter(()=> {})
numbers.reduce(() => 0)
numbers.map(() => 0)
export interface FindProductDto extends Readonly<Partial<Omit<Product, 'tags'>>> {
tags: ReadonlyArray<string>;
}
// no nos permitira hacer un
FindProductDto.tags?.pop()
// Pero si una reasignación
FindProductDto.tags = []
// Para evitar lo anterior, usaremos readOnly en el atributo
export interface FindProductDto extends Readonly<Partial<Omit<Product, 'tags'>>> {
readonly tags: ReadonlyArray<string>;
}
Promesas: Indicando el tipo de retorno
function delay (time: number) {
// Bastara con indicar el type entre <> para que TS sepa que type
// va a retornar esa promesa
const promise = new Promise<string>((resolve) => {
setTimeout(() => {
resolve('string');
}, time);
});
return promise;
}
Genéricos
¿qué ocurre si quiere crear un componente que funcione con una variedad de tipos en lugar de solo con uno? Puede usar el tipo any, pero entonces no podrá aprovechar la ventaja del sistema de comprobación de tipos de TypeScript. ver mas
Los genéricos nos permite definir de forma dinámica el tipado, esto es similar a los argumentos que se pasan a una función, pero se utilizan para indicar el tipo de datos que se va a utilizar en cada llamado que hagamos
Cree funciones genéricas cuando el código sea una función o una clase que:
- Funcione con varios tipos de datos.
- Use ese tipo de datos en varios lugares.
Los genéricos pueden:
- Proporcionar más flexibilidad a la hora de trabajar con tipos.
- Permitir la reutilización de código.
- Reducir la necesidad de usar el tipo
any
.
// Ejemplo 1
// La funcion getArray recibira un Tipo de forma dinámica
// llamado MyType y que sera utilizado dentro de la funcion
// multiples veces
function getArray<MyType>(items : MyType[]) : MyType[] {
return new Array<MyType>().concat(items);
}
// asi la usamos con number
let numberArray = getArray<number>([5, 10, 15, 20]);
// asi la usamos con string
let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);
// Ejemplo 2
// la función identity acepta dos parámetros, value y message, y devuelve
// el parámetro value. Puede usar dos genéricos, T y U, para asignar
// distintos tipos a cada parámetro y al tipo de valor devuelto.
function identity<T, U> (value: T, message: U) : T {
console.log(message);
return value
}
let returnNumber = identity<number, string>(100, 'Hello!');
let returnString = identity<string, string>('100', 'Hola!');
let returnBoolean = identity<boolean, string>(true, 'Bonjour!');
returnNumber = returnNumber * 100; // OK
returnString = returnString * 100; // Error: Type 'number' not assignable to type 'string'
returnBoolean = returnBoolean * 100; // Error: Type 'number' not assignable to type 'boolean'
Genericos en clases
import axios from 'axios';
import { UpdateProductDto } from '../dtos/product.dto';
import { Category } from './../models/category.model';
import { Product } from './../models/product.model';
// La clase puede recibir tipos dinamicamente
export class BaseHttpService<TypeClass> {
constructor(
protected url: string
) {}
async getAll() {
const { data } = await axios.get<TypeClass[]>(this.url);
return data;
}
// Los métodos también pueden recibir tipos de forma dinamica
async update<ID, DTO>(id: ID, changes: DTO) {
const { data } = await axios.put(`${this.url}/${id}`, changes);
return data;
}
}
(async ()=> {
const url1 = 'https://api.escuelajs.co/api/v1/products';
// Aca al utilizar el generico, podemos implementar un service
// basado en Product
const productService = new BaseHttpService<Product>(url1);
const rta = await productService.getAll();
console.log('products', rta.length);
// Indicamos los tipos de forma dinamica al llamar al método
productService.update<Product['id'], UpdateProductDto>(1, {
title: 'asa',
});
const url2 = 'https://api.escuelajs.co/api/v1/categories';
// Aca al utilizar el generico, podemos implementar un service
// basado en Category sin tener que modificar código
const categoryService = new BaseHttpService<Category>(url2);
const rta1 = await categoryService.getAll();
console.log('categories', rta1.length);
})();