Skip to content

TypeScript ​

TypeScript es un lenguaje de programación de código abierto desarrollado y mantenido por Microsoft. Es un superconjunto de JavaScript, que esencialmente añade tipado estático y objetos basados en clases.

También es un compilador que traduce código TypeScript a JavaScript. El código TypeScript se compila a JavaScript, que es el código que se ejecuta en el navegador.

Podemos pensar que Typescript como un linter, que nos ayuda a detectar errores en tiempo de compilación.

Ventajas de TypeScript ​

  • Tipado estático. TypeScript añade tipado estático a JavaScript. El tipado estático permite detectar ciertas categorías de errores en tiempo de compilación. Por ejemplo, intentar asignar un string a una variable de tipo number dará un error en tiempo de compilación.
ts
function sum(a: number, b: number) {
  return a + b;
}

sum(1, 2); // 3
sum("1", "2"); // Error: Argument of type '"1"' is not assignable to parameter of type 'number'.
function sum(a: number, b: number) {
  return a + b;
}

sum(1, 2); // 3
sum("1", "2"); // Error: Argument of type '"1"' is not assignable to parameter of type 'number'.

Al tipar las variables, podemos saber los métodos y propiedades. De esta manera el IDE puede ayudarnos a programar.

Desventajas de TypeScript ​

  • Curva de aprendizaje. TypeScript añade una capa de complejidad a JavaScript. Aunque la sintaxis es muy similar, hay que aprender a usar los tipos, interfaces, clases, etc.

  • Requiere compilación. TypeScript no se puede ejecutar directamente en el navegador. Primero hay que compilarlo a JavaScript. Esto añade un paso más al flujo de trabajo.

  • No se ejecutan las validaciones en runtime. Las validaciones de tipos se hacen en tiempo de compilación. En tiempo de ejecución no hay validaciones de tipos.

Visual Studio Code ​

Visual Studio Code es un editor de código fuente desarrollado por Microsoft para Windows, Linux y macOS.

Este editor por defecto, aunque estemos trabajando en un archivo .js, utiliza el motor de TypeScript para mostarnos los tooltips, mientras estamos programando.

Instalación ​

Para instalar TypeScript, necesitamos tener instalado Node.js.

bash
npm install -g typescript
npm install -g typescript

tsconfig.json

json
{
  "compilerOptions": {
    "outDir": "./dist", // Carpeta donde se compilará el código
    "target": "ES6", // Versión de JavaScript a la que se compilará. Muy parecido a Babel
    "declaration": true, // Genera los archivos de definición de tipos
    "allowJs": true, // Permite convivir con JavaScript
    "lib": ["DOM", "DOM.Iterable", "ESNext"] // Librerías que queremos que estén disponibles
  },
  "include": ["src"] // Carpeta donde se encuentra el código fuente
}
{
  "compilerOptions": {
    "outDir": "./dist", // Carpeta donde se compilará el código
    "target": "ES6", // Versión de JavaScript a la que se compilará. Muy parecido a Babel
    "declaration": true, // Genera los archivos de definición de tipos
    "allowJs": true, // Permite convivir con JavaScript
    "lib": ["DOM", "DOM.Iterable", "ESNext"] // Librerías que queremos que estén disponibles
  },
  "include": ["src"] // Carpeta donde se encuentra el código fuente
}

Compilación

bash
tsc
tsc

El compilador de TS, tsc, no minifica el código... Para ello podemos usar Webpack.

En Webpack, podemos usar el plugin ts-loader para que compile TypeScript.

js
resolve: {
  extensions: [".ts", ".js"],
},
module: {
  rules: [
    {
      test: /\.ts$/,
      exclude: /node_modules/,
      use: {
        loader: "ts-loader",
        options: {
          transpileOnly: true, // Que al momento de hacer la build, no compruebe los tipos. Eso es útil cuando estamos pasando codigo de JS a TS
        },
      },
    }
  ]
}
resolve: {
  extensions: [".ts", ".js"],
},
module: {
  rules: [
    {
      test: /\.ts$/,
      exclude: /node_modules/,
      use: {
        loader: "ts-loader",
        options: {
          transpileOnly: true, // Que al momento de hacer la build, no compruebe los tipos. Eso es útil cuando estamos pasando codigo de JS a TS
        },
      },
    }
  ]
}

Tipos de datos ​

Boolean ​

ts
let isDone: boolean = false;
let isDone: boolean = false;

Number ​

ts
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

String ​

ts
let color: string = "blue";
color = "red";
let color: string = "blue";
color = "red";

Array ​

ts
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

Destructuring ​

ts
const myCar = [2002, "Ford", "Focus"];

const [year, make, model] = myCar;
const myCar = [2002, "Ford", "Focus"];

const [year, make, model] = myCar;

El problema es que cada valor destructurado, lo entiende como string o number.

Por lo que tenemos que hacer un casting.

ts
const myCar = [2002, "Ford", "Focus"];

const [year, make, model] = myCar as [number, string, string];
const myCar = [2002, "Ford", "Focus"];

const [year, make, model] = myCar as [number, string, string];

O bien podemos hacer un type alias.

ts
type Car = [number, string, string];

const myCar = [2002, "Ford", "Focus"] as Car;

const [year, make, model] = myCar;
type Car = [number, string, string];

const myCar = [2002, "Ford", "Focus"] as Car;

const [year, make, model] = myCar;

Tuple ​

ts
let x: [string, number];
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error
let x: [string, number];
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error

Readonly ​

ts
let x: readonly [string, number] = ["hello", 10];
x[0] = "world"; // Error
let x: readonly [string, number] = ["hello", 10];
x[0] = "world"; // Error

Enum ​

ts
enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green;
enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green;

Any ​

ts
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

Void ​

ts
function warnUser(): void {
  console.log("This is my warning message");
}
function warnUser(): void {
  console.log("This is my warning message");
}

Null and Undefined ​

ts
let u: undefined = undefined;
let n: null = null;
let u: undefined = undefined;
let n: null = null;

Never ​

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

Object ​

ts
declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error

Index Signatures ​

ts
const x: { [key: string]: number } = {};
x.a = 1;
x["b"] = 2;
const x: { [key: string]: number } = {};
x.a = 1;
x["b"] = 2;

En objetos, podemos usar index signatures para definir propiedades dinámicas.

ts
interface Person {
  name: string;
  age: number;
  [key: string]: any;
}

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John", age: 30, address: "Main Street" });
interface Person {
  name: string;
  age: number;
  [key: string]: any;
}

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John", age: 30, address: "Main Street" });

En objetos complejos.

ts
const phones: { [key: string]: { brand: string; model: string } } = {
  iPhone: { brand: "Apple", model: "iPhone 12" },
  Galaxy: { brand: "Samsung", model: "Galaxy S21" },
  Pixel: { brand: "Google", model: "Pixel 5" },
};
const phones: { [key: string]: { brand: string; model: string } } = {
  iPhone: { brand: "Apple", model: "iPhone 12" },
  Galaxy: { brand: "Samsung", model: "Galaxy S21" },
  Pixel: { brand: "Google", model: "Pixel 5" },
};

Cuando hacemos key: string, estamos diciendo que la key puede ser cualquier string. Pero podemos ser más específicos. Ya que, si no acepta cualquier string, aunque no exista en el objeto, no dará error.

ts
phones.iPhone; // { brand: 'Apple', model: 'iPhone 12' }
phones.android; // undefined, pero no da error
phones.iPhone; // { brand: 'Apple', model: 'iPhone 12' }
phones.android; // undefined, pero no da error
ts
const phones: {
  [key in "iPhone" | "Galaxy" | "Pixel"]: { brand: string; model: string };
} = {
  iPhone: { brand: "Apple", model: "iPhone 12" },
  Galaxy: { brand: "Samsung", model: "Galaxy S21" },
  Pixel: { brand: "Google", model: "Pixel 5" },
};

phones.iPhone; // { brand: 'Apple', model: 'iPhone 12' }
phones.android; // Error
const phones: {
  [key in "iPhone" | "Galaxy" | "Pixel"]: { brand: string; model: string };
} = {
  iPhone: { brand: "Apple", model: "iPhone 12" },
  Galaxy: { brand: "Samsung", model: "Galaxy S21" },
  Pixel: { brand: "Google", model: "Pixel 5" },
};

phones.iPhone; // { brand: 'Apple', model: 'iPhone 12' }
phones.android; // Error

Type assertions ​

ts
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;

Static Typing ​

El motor se encarga de comprobar en tiempo de desarrollo que los tipos de datos son correctos.

ts
function sum(a: number, b: number) {
  return a + b;
}

sum(1, 2); // 3
sum("1", "2"); // Error: Argument of type '"1"' is not assignable to parameter of type 'number'.
function sum(a: number, b: number) {
  return a + b;
}

sum(1, 2); // 3
sum("1", "2"); // Error: Argument of type '"1"' is not assignable to parameter of type 'number'.

Inferencia de tipos ​

TypeScript le asigna un tipo a cada variable, aunque no lo especifiquemos. Inferencia de tipos.

ts
let a = 1; // number
let b = "1"; // string
let c = true; // boolean
let a = 1; // number
let b = "1"; // string
let c = true; // boolean

Tipos literales ​

ts
let humidity = 79 as 79; // 79
let humidity = 79 as const; // 79
let humidity = 79 as 79; // 79
let humidity = 79 as const; // 79

Any y type casting ​

ts
let a: any = 1;
let b = a as number;
let c = <number>a;
let a: any = 1;
let b = a as number;
let c = <number>a;

Intersections ​

En TypeScript, podemos hacer uniones de tipos con el operador &.

ts
interface A {
  a: number;
}

interface B {
  b: number;
}

type C = A & B;

const c: C = { a: 1, b: 2 };
// const c: C = { a: 1 }; // Error
interface A {
  a: number;
}

interface B {
  b: number;
}

type C = A & B;

const c: C = { a: 1, b: 2 };
// const c: C = { a: 1 }; // Error

Interface y Type ​

La diferencia entre interface y type

Las interfaces son más flexibles y se pueden extender.

ts
interface A {
  a: number;
}

interface B {
  b: number;
}

interface C extends A, B {
  c: number;
}
interface A {
  a: number;
}

interface B {
  b: number;
}

interface C extends A, B {
  c: number;
}

Los interfaces solo sirven para definir la forma de un objeto. Por lo que cuando vamos a definir un tipo primitivo, siempre vamos a usar type.

Con los type podemos hacer uniones, intersecciones, etc.

ts
type A = number | string;

type B = A & { b: number };

type C = A | { c: number };
type A = number | string;

type B = A & { b: number };

type C = A | { c: number };

Las interfaces se pueden declarar varias veces, y se van a ir extendiendo. Los type no.

ts
interface A {
  a: number;
}

interface A {
  b: number;
}

const a: A = { a: 1, b: 2 };

type B = { a: number };

type B = { b: number }; // Error
interface A {
  a: number;
}

interface A {
  b: number;
}

const a: A = { a: 1, b: 2 };

type B = { a: number };

type B = { b: number }; // Error

Interfaces ​

Las interfaces son una forma poderosa de definir contratos en TypeScript. Las interfaces son un tipo de dato que define una estructura de datos.

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

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John", age: 30 });
interface Person {
  name: string;
  age: number;
}

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John", age: 30 });

Propiedades opcionales ​

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

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John" });
interface Person {
  name: string;
  age?: number;
}

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John" });

Anidadas ​

ts
interface User {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

interface UserResponse {
  success: boolean;
  user: User;
}

function printUser(data: UserResponse) {
  console.log(data.user.name, data.user.address.city, data.success);
}

printUser({
  success: true,
  user: { name: "John", address: { street: "Main Street", city: "New York" } },
});
interface User {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

interface UserResponse {
  success: boolean;
  user: User;
}

function printUser(data: UserResponse) {
  console.log(data.user.name, data.user.address.city, data.success);
}

printUser({
  success: true,
  user: { name: "John", address: { street: "Main Street", city: "New York" } },
});

Index Signatures ​

ts
interface Person {
  name: string;
  age: number;
  [key: string]: any;
}

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John", age: 30, address: "Main Street" });
interface Person {
  name: string;
  age: number;
  [key: string]: any;
}

function printPerson(person: Person) {
  console.log(person.name, person.age);
}

printPerson({ name: "John", age: 30, address: "Main Street" });

Extends ​

ts
interface A {
  a: number;
}

interface B {
  b: number;
}

interface C extends A, B {
  c: number;
}

const c: C = { a: 1, b: 2, c: 3 };
interface A {
  a: number;
}

interface B {
  b: number;
}

interface C extends A, B {
  c: number;
}

const c: C = { a: 1, b: 2, c: 3 };

Implements ​

La diferencia entre extends e implements es que extends se usa para extender una interface, y implements se usa para implementar una interfaz.

ts
interface A {
  a: number;
}

interface B {
  b: number;
}

class C implements A, B {
  a = 1;
  b = 2;
}
interface A {
  a: number;
}

interface B {
  b: number;
}

class C implements A, B {
  a = 1;
  b = 2;
}

Open interfaces ​

Las interfaces son abiertas, por lo que se pueden declarar varias veces, y se van a ir extendiendo.

ts
interface A {
  a: number;
}

interface A {
  b: number;
}

const a: A = { a: 1, b: 2 };
interface A {
  a: number;
}

interface A {
  b: number;
}

const a: A = { a: 1, b: 2 };

Global ​

Podemos declarar interfaces globales, y luego extenderlas.

ts
declare global {
  interface Window {
    myVar: string;
  }
}

window.myVar = "Hello";
declare global {
  interface Window {
    myVar: string;
  }
}

window.myVar = "Hello";

Type ​

Puede contener cualquier tipo de dato, incluyendo primitivos, objetos, funciones, etc. Se puede usar para definir tipos primitivos, tipos de unión, tipos de intersección, etc.

ts
type A = number | string;

type B = A & { b: number };

type C = A | { c: number };
type A = number | string;

type B = A & { b: number };

type C = A | { c: number };

Implements ​

ts
type A = {
  a: number;
};

type B = {
  b: number;
};

class C implements A, B {
  a = 1;
  b = 2;
}
type A = {
  a: number;
};

type B = {
  b: number;
};

class C implements A, B {
  a = 1;
  b = 2;
}

Aunque no es recomendable, ya que los type pueden ser tipos primitivos, y no tienen sentido implementarlos. En este caso es mejor usar para los implementos las interfaces.

ts
type A = number;

type B = {
  b: number;
};

class C implements A, B {
  // Error
  a = 1;
  b = 2;
}
type A = number;

type B = {
  b: number;
};

class C implements A, B {
  // Error
  a = 1;
  b = 2;
}

Recursión ​

Podemos encontrar recursión simple.

ts
type NestedNumbers = number | NestedNumbers[];

const numbers: NestedNumbers = [1, [2, [3, 4], 5], 6];
type NestedNumbers = number | NestedNumbers[];

const numbers: NestedNumbers = [1, [2, [3, 4], 5], 6];

En este caso estamos definiendo un tipo que puede ser un número o un array de números, y a su vez, cada número puede ser un array de números.