Instalación
Typescript necesita ser instalado para su utilización, a su vez se necesita npm
para gestionar el paquete.
Para saber si tiene npm instalado se tiene que abrir una consola y en el mismo pegar el siguiente comando.
npm --v
Esto dará como resultado la version actual del mismo, de no ser asi, se instalar con el siguiente comando.
npm i -g
Luego de instalarlo, el siguiente paso es instalar TypeScript, en la misma consola pegar el siguiente comando.
npm i typescript -g
El flag
-g
indica que el mismo se instalará globalmente, si queremos instalarlo en un solo proyecto deberemos abrir la consola en el mismo e instalarlo sin esta flag.
En teoría debería haberse instalado TS, pero siempre es mejor asegurarse escribiendo tsc -v
en la misma consola, el cual debería mostrar la version instalada del mismo.
Compilador de JS/TS
Para iniciar con el lenguaje se debe crear un archivo con la extension .ts
, luego en la consola se deberá iniciar TypeScript con el siguiente comando.
tsc --init
Esto creará un archivo de configuración (tsconfig.json), el cual contiene todas las configuraciones y opciones necesarias para el funcionamiento del compilador en el archivo actual.
Luego del mismo se deberá compilar el archivo ts
en un archivo js
, esto se realiza pegando el siguiente comando en la consola.
tsc
Es posible compilar un solo archivo
ts
en el proyecto, nombrándolo luego deltsc
=>tsc archivo.ts
Se creará un archivo js
, el cual sera la compilación del código escrito en el archivo de TypeScript.
Coding
Luego de esta introducción e instalación de TS es momento del código, se puede empezar por escribir un simple código de JavaScript en el archivo ts
.
function addNumbers(x, y) {
return x + y;
}
console.log(addNumbers(3, 6));
El código es una función normal que suma los parámetros, pero hay un error notable en el mismo, dado a que TS esta hecho para un tipado marcado, es necesario declarar los tipos de variables que se usan, quedando el mismo de la siguiente forma.
function addNumbers(x: number, y: number) {
return x + y;
}
console.log(addNumbers(3, 6));
Al agregar el tipo de la variable se evitan los futuros problemas al pasar valores que no sean de tipo declarado, es decir Number.
Luego de esto, se debe compilar el archivo, el cual se hace escribiendo tsc
en la consola, si no hay errores lo compilara sin problemas, y el resultado debería ser el siguiente.
"use strict";
function addNumbers(x, y) {
return x + y;
}
console.log(addNumbers(3, 6));
Como es posible observar, el código es básicamente idéntico, se diferencia en el "use strict"
() y en que el tipado de valores puestos en el archivo ts
ya no se encuentra, pero el mismo funciona a la perfección.
El archivo
js
ya se puede linkear como cualquier otrojs
a un HTML, y debería funcionar normalmente.
Tipos
TypeScript no solo funciona en base a los tipos que existen en JavaScript, sino que agrega nuevos tipos de datos, uno de ellos es el tipo enum
.
Enum
es un tipo de dato que se comporta como un array/clase, el cual guarda los posibles valores relacionados que se pueden utilizar en un rango, como pueden ser los días en una semana, o las posibles acciones de un click del mouse.
enum weekDays {
Monday, //0
Tuesday, //1
Wednesday, //2
Thursday, //3
Friday, //4
Saturday, //5
Sunday, //6
}
console.log(weekDays.Wednesday);
La consola mostrará el resultado como un valor numérico (en este caso 2).
Como los arrays normales, los enum
comienzan en 0, pero es posible empezar la lista con un valor a elección, y los siguientes números serán asignados automáticamente en base al mismo.
enum StatusCodes {
OK = 200,
BadRequest = 400,
Unauthorized, //401
PaymentRequired, //402
Forbidden, //403
NotFound, //404
}
console.log(statusCode.NotFound);
La consola mostrará en este caso el numero 404, ya que es el valor que se le asigna automáticamente.
Por ultimo, también hay que aclarar que es posible asignar un valor de tipo string
, pero no sera posible que se asignen los siguientes automáticamente, pero al imprimir los valores con console.log
se verán mas fácilmente, sin tener que ir a ver el valor asignado al numero en cuestión.
enum Users {
user = "USER",
admin = "ADMIN",
superAdmin = "SUPERADMIN",
}
console.log(Users.admin);
En este caso, la consola mostrará
ADMIN
, en vez de imprimir el valor que tendría según la posición numérica.
Otro tipo de valor asignable es el tipo any
, el cual declara que el valor asignable puede ser literalmente de cualquier tipo, ya sea Number
, string
, boolean
, etc.
let randomValue: any = 10;
randomValue = "Luke"; // OK
randomValue = true; // OK
La reasignación de datos a las variables no deriva en un error, gracias al tipo any
, que es lo que sucede al hacer código de JavaScript normalmente.
El tipo any
puede dar algunos errores, para ello se creo el tipo unknown
, el cual es similar al tipo anteriormente nombrado, pero evitando que se pueda acceder al mismo, unicamente reasignarle un valor.
let randomValue: unknown = 10;
randomValue = true;
randomValue = "Luke";
randomValue = { name: "Exodia", parts: 5 };
console.log(randomValue.name); // ERROR
Esto se “soluciona” de dos formas, asignándole un valor con el método as
.
El método as
sirve para forzar el tipo de valor, siempre y cuando sea correcto.
let randomValue: unknown = 10;
randomValue = true;
randomValue = "Luke";
randomValue = "Skywalker";
console.log(randomValue as string); // Skywalker
Cuando no se declara el valor inicial como unknown
, es posible reasignarlo como tal posteriormente.
let randomValue: unknown = 10;
randomValue = true;
randomValue = "Luke";
randomValue = "Skywalker";
let randomValueForError = 19;
console.log(randomValue as string); // Skywalker
console.log(randomValueForError as string); // ERROR, 19 no es un string
console.log(randomValueForError as unknown as string); // OK, ya que primero se declara como UNKNOWN
También es posible declararlo con una tag (console.log(
<string>
randomValue)
La 2da forma de comprobar es hacer una comprobación directa de js
, usando un typeOf
.
let randomValue: unknown = 10;
randomValue = true;
randomValue = "Luke";
randomValue = "Skywalker";
if (typeof randomValue === "string") {
console.log(randomValue.toUpperCase()); // SKYWALKER
}
Esta comprobación se realiza luego de la compilación a JavaScript.
Dado que hay ciertos momentos en los que el control de datos que se ingresan al código pueden ser medianamente controlados, se puede usar la union
(|
) para declarar dos o mas tipos de datos aceptados.
let randomValue: number | boolean = 10; // OK
randomValue = true; // OK
randomValue = "Luke"; // ERROR
Otra forma de unir tipos es la intersección
(&
), la cual en vez de elegir entre uno o el otro, junta ambos tipos. Para el ejemplo del mismo se van a utilizar Interfaces
, pero, ¿Que son las interfaces?.
Interfaces
Las interfaces
son un tipo de estructura que definen las características que va a tener un objeto, siendo similar a las clases, pero con datos obligatorios.
Las interfaces se declaran con la palabra reservada interface
y el nombre.
interface Character {
name: string; // OBLIGATORIO
surname: string; // OBLIGATORIO
age: number; // OBLIGATORIO
country?: string; // OPCIONAL
}
let person1: Character = {
name: "Jotaro",
surname: "Kujo",
status: "active", // ERROR, no se reconoce porque no esta en la interface
age: 40,
};
Como se ve en el ejemplo, el dato
country
no es necesario (?:
), pero el resto si.
También es posible crear interfaces que requieran funciones con ciertos nombres predefinidos.
interface Fly {
fly(): void;
// fly: () => void // Es otra forma de declarar las funciones
}
let action: Fly = {
fly: function () {
console.log("I believe I can fly");
},
};
action.fly();
Sumado a esto es posible editar las interfaces simplemente volviendo a declarar una interface con el mismo nombre de la siguiente manera
interface Fly {
species: string;
}
Las interfaces pueden juntarse para no repetir las mismas propiedades si una ya las tiene, dando asi un mejor flow a la hora de escribir el código. Esto es posible hacerlo con la palabra reservada extends
, extendiendo todas las propiedades anteriormente nombradas.
interface Character {
name: string; // OBLIGATORIO
surname: string; // OBLIGATORIO
age: number; // OBLIGATORIO
country?: string; // OPCIONAL
}
interface FirstAppearance extends Character {
chapter: string;
publisher: string;
year: number;
}
let person1: FirstAppearance = {
name: "Jotaro",
surname: "Kujo",
age: 40,
country: "Japan",
chapter: "114 'Jotaro Kujo (1)'",
publisher: "Weekly Shōnen Jump",
year: 1989,
};
Intersecciones
Las interfaces pueden hacer el uso de la intersección, el mismo es el método utilizado para combinar dos tipos de interfaces, dando como resultado una nueva de la siguiente manera.
interface Employee {
employeeID: number;
age: number;
}
interface Manager {
stockPlan: boolean;
}
let newManager: Employee & Manager = {
employeeID: 12345,
age: 34,
stockPlan: true,
};
También es posible crear un type
especifico uniendo ambos, para crear la validación de los datos necesarios.
interface Employee {
employeeID: number;
age: number;
}
interface Manager {
stockPlan: boolean;
}
type ManagementEmployee = Employee & Manager;
let newManager: ManagementEmployee = {
employeeID: 12345,
age: 34,
stockPlan: true,
};
El resultado sigue siendo el mismo, pero el nuevo
type
puede ser utilizado en mas lugares sin la necesidad de repetir la intersección, lo cual se explicará a continuación
Literales
Habrá momentos en los que sea necesario asignar un valor especifico de una lista de valores, esto es posible gracias a la definición de tipos literales.
Los tipos literales brindan la posibilidad de que se declare un error si se pasa un valor que no este anteriormente declarado aunque el mismo sea sintácticamente valido.
type testResult = "pass" | "fail" | "incomplete";
let myResult: testResult;
myResult = "incomplete"; // OK
myResult = "pass"; // OK
myResult = "failure"; // ERROR
También es posible pasar valores
boolean
ynumber
como tipos literales
Arrays
Como todos los otros datos, los arrays también tienen que declarar los tipos que contendrán, evitando que se agreguen tipos indeseados.
const numbers: number[] = [1, 2, 3, 4, 5]; // OK
const vowels: string[] = ["a", "e", "i", "o", "u"]; // OK
const surelyVowels: string[] = ["a", 3, 1, 0, "u"]; // ERROR, no se puede pasar un number como valor
Sumado a esto, hay un tipo especial de array que se caracteriza por tener una longitud definida de valores, con su tipo marcado en cada uno de ellos, los cuales se conocen como tuples
.
const life: [number, string] = [1, "UP"]; // OK
const wrong: [number, string, number] = [4, "GET", "ME"]; // ERROR, no es de tipo number
const missingNo: [string, number] = ["404"]; // ERROR, falta un dato
Los
tuples
tienen que contener sus datos como fueron asignados, en orden y cantidad, sino da ERROR como resultado
También existe la posibilidad de nombrar tuples
para tener un mejor contexto de los mismos, y generar un destructuring
de sus datos.
const persona: [nombre: string, apellido: string, edad: number] = [
"Lara",
"Croft",
26,
];
const [primerNombre, primerApellido, edadActual] = persona;
console.log(primerApellido); // "CROFT", el valor dado por el destructuring
ReadOnly
Es posible declarar arrays que sean solo para lectura, es decir, que no se puedan modificar. Esto se logra gracias a la palabra reservada readonly
.
const numbers: number[] = [1, 2, 3, 4, 5];
const codes: readonly number[] = [200, 400, 404, 500];
numbers.push(4);
codes.push(403); // ERROR, no se puede modificar un array con readonly
readonly
también es aplicable a lostuples
, dotando a los mismos de una capa mas de comprobación
Los objetos en ts
también tienen su sintaxis definida, la cual cambia un poco a como es en js
.
const house: {
address: string;
city: string;
price: number;
pets: boolean;
website: string;
weekly?: boolean;
} = {
address: "39 Niagara Street",
city: "Toronto",
pets: true,
price: 4000,
website: "https://rentals.ca/toronto/niagara-west",
};
Cuando se declara una variable con el símbolo
?
significa que el mismo puede ser omitido, ya que no es obligatorio
Si no se sabe la cantidad exacta de valores que contendrá un objeto es posible definir todos sus valores, independientemente de la cantidad.
const namesAndAge: { [index: string]: number } = {};
namesAndAge.Alucard = 598;
namesAndAge.SerasVictoria = 19;
namesAndAge.IntegraFairbrookWingatesHellsing = 22;
Funciones
Las funciones en ts
también pueden tener los tipos de entrada y salida marcados, su sintaxis base es la misma que en js
, solo que se le agrega que tipo de dato vamos a pasar como parámetro (dentro de los paréntesis) y que tipo de dato retornara (luego de los paréntesis), esto también aplica para las funciones anónimas y las arrow functions.
function suma(a: number, b: number): number {
console.log(a + b);
return a + b;
}
let anonSuma = function (c: number, d: number): number {
console.log(c + d);
return c + d;
};
let arrowSuma = (e: number, f: number): number => e + f;
console.log(arrowSuma(5, 7));
Sumado a esto, es posible crear funciones que no retornen ningún valor explicito, a esto se le llama void
.
function sayMyName(name: string): void {
if (name.toLowerCase() === "heisenberg") {
console.log("You're goddamn right");
} else {
console.log(":gun:");
}
}
sayMyName("heisenberg");
Clases
Algo muy característico de la OOP son las clases, estas mismas pueden ser utilizadas en ts
, pero su implementación cambia ligeramente, ya que ademas de que se tienen que declarar los tipos de las propiedades, también se tiene que declarar el tipo de dato que se utilizará en los métodos.
class CardsDeck {
conName: string;
conCardsQty: number;
conBrand: string;
constructor(name: string, cardsQty: number, brand: string) {
this.conName = name;
this.conCardsQty = cardsQty;
this.conBrand = brand;
}
giveMeOne(max = this.conCardsQty): number {
let yourCard: number = Math.floor(Math.random() * (max - 1 + 1) + 1); // método para retornar un numero al azar
return yourCard;
}
}
const black = new CardsDeck("Samurai", 56, "Bicycle");
console.log(black.giveMeOne());
Las clases pueden tener sus propiedades con 3 etiquetas, public
(la que se usa normalmente, pudiendo acceder y modificarlas desde cualquier lado), private
(solo pueden acceder y modificarse en la misma clase) y por ultimo protected
(solo pueden acceder y modificarse en la misma clase o cuando viene de una clase padre).
class Person {
private conName: string; // Solo en la clase
public conSurname: string; // Desde cualquier lado
protected conWorking: boolean; // Solo en la clase y al heredarse
constructor(name: string, surname: string) {
this.conName = name;
this.conSurname = surname;
this.conWorking = true;
}
public getConName(): string {
return this.conName;
}
}
class Employee extends Person {
conId: number;
constructor(id: number, name: string, surname: string) {
super(name, surname);
this.conId = id;
}
isWorker(): string {
if (this.conWorking) {
return "Es un empleado modelo";
} else {
return "No es posible llegar a este error sin modificar el código >:(";
}
}
}
let first = new Employee(1523, "Walter", "White");
console.log(first.conName); //ERROR, no se puede acceder desde fuera
console.log(first.getConName()); // Walter
console.log(first.isWorker()); // Es un empleado modelo
Es posible agregar
readonly
para evitar la reasignación de datos (private readonly conName: string
)
En las clases es posible incluir interfaces con la palabra reservada implements
.
interface Character {
name: string;
surname: string;
age: number;
country?: string;
}
class Person implements Character {
name: string;
surname: string;
age: number;
constructor(name: string, surname: string, age: number) {
this.name = name;
this.surname = surname;
this.age = age;
}
public getData(): string {
return `${this.name} ${this.surname}, ${this.age} años`;
}
}
let first = new Person("Walter", "White", 52);
console.log(first.getData());
Es posible crear clases que sirvan unicamente como bases para crear otras bases, a estas se las conoce como abstract
, y se las implementa nuevamente con el uso de la palabra reservada extends
.
abstract class Animal {
type: string;
name: string;
constructor(type: string, name: string) {
this.type = type;
this.name = name;
}
abstract makeNoise(): string;
}
class Cat extends Animal {
makeNoise(): string {
return "meow";
}
}
let white = new Animal("dog", "White"); // ERROR, las clases abstractas no se pueden llamar, solo usar en otras clases
let black = new Cat("feline", "Black");
console.log(black.makeNoise()); // meow
Generic types
Hay veces que necesitamos crear funciones o interfaces bases pero sin tener del todo claro que tipo de datos vamos a usar desde un principio, para eso se utilizan los generic types
, los cuales sirven para crear tipos que se declararan en su uso y pueden reutilizarse.
interface savedData<T> {
name: string;
id: number;
data: T;
}
const person1: savedData<object> = {
name: "Luci",
id: 616,
data: { age: "unknown", status: "alive" },
};
Es posible declarar un tipo default agregando = al generic
<T = string>
Ademas, es posible declarar los tipos de variables que se pueden usar desde un inicio, evitando poder pasar un tipo indeseado cuando se use.
interface savedData<T extends object | string> {
name: string;
id: number;
data: T;
}
const person1: savedData<object> = {
name: "Lucy",
id: 616,
data: { age: "unknown", status: "alive" },
};
const person2: savedData<string> = {
name: "white shadow",
id: 111,
data: "unknown",
};
const person3: savedData<number> = {
// ERROR, number no es un tipo que se haya declarado en el generic
name: "test",
id: 7357,
data: 0,
};
Selección de datos
Hay veces que necesitamos pasar solamente algunos datos de una interface, sin que todos sean obligatorios, para ello podemos usar la palabra reservada Partial
, el cual evita que sean obligatorios.
interface savedData<T> {
name: string;
id: number;
data: T;
}
const person1: Partial<savedData<object>> = {
name: "Lucy",
data: { age: "unknown", status: "alive" }, // Falta el ID, pero al ser Partial deja de ser necesario
};
Para el efecto contrario se utiliza Required
.
interface Person {
name: string;
surname: string;
code?: number;
}
let newPerson: Required<Person> = {
name: "James",
surname: "Bond",
}; // ERROR, falta code ya que el Required lo convirtió en obligatorio
Record
se utiliza para marcar como deben de ser estructurados los datos que se pasaran a un objeto.
const errors: Record<number, string> = {
204: "No content",
400: "Bad request",
404: "Not found",
};
También es posible llegar al mismo resultado usando
{[key: number]: string}
Es posible eliminar un dato necesario de una interface, para ello se utiliza Omit
, el cual evita usar el valor que se declare en el mismo.
interface savedData {
name: string;
id: number;
data: object;
}
const person1: Omit<savedData, "name" | "id"> = {
name: "Lucy", // ERROR, Omit elimino este valor asi que no puede ser aplicado
id: 616, // ERROR, Omit elimino este valor asi que no puede ser aplicado
data: { age: "unknown", status: "alive" },
};
Al contrario de este existe Pick
, el cual solo declara los valores elegidos.
interface savedData {
name: string;
id: number;
data: object;
}
const person1: Pick<savedData, "name"> = {
name: "Lucy",
id: 616, // ERROR, solo se eligió el valor name para declararse
};