Fecha publicación: 25 sept 2021

TypeScript Interfaces

En el video anterior vimos como el tipo object era algo muy genérico y se nos quedaba corto a la hora de poder crear contratos que nos permitan definir objetos, en esta lección vamos a ver el soporte que da TypeScript a la hora de crear interfaces.

Manos a la obra

La forma que tenemos en TypeScript de tipar objetos es mediante los interfaces. Un interfaz no es más que un contrato especificando las propiedades que un objeto debe tener, para tipar un interfaz escribimos la palabra clave interface y a continuación el nombre de nuestro tipo. Vamos a codificar un ejemplo: en este caso ponemos GeoPosition como nombre del interface (recordad Pascal Case ya que no es un tipo primitivo), abrimos y cerramos con llaves como si se tratara de un objeto, y aquí vamos a colorar cada una de la propiedades con sus tipados, en este caso ponemos dos propiedades latitude de tipo number y longitude de tipo number.

interface GeoPosition {
  latitude: number;
  longitude: number;
}

Ya hemos creado nuestro interfaz, pero esto es simplemente eso, un definición de interfaza, un tipo ¿Cómo generamos las instancias de ese tipo? Muy sencillo, seguimos con el ejemplo, creamos un objeto pos y le decimos que va a ser de tipo GeoPosition, en cuanto tecleamos esto, fijaos que en el momento que queremos inicializar nuestros objeto el intellisense de nuestro editor nos va a decir que hay dos propiedades que tenemos que incorporar y con tipo tienen, si yo escribo latitude sólo me va a dejar escribir un número, si por ejemplo introduzco string se me marca como error (me dice que no es asignable a tipo number), de igual modo necesito colocar la longitud.

Tipamos la variable tenemos en el intellisense las propiedades latitude y longitude

Código:

const pos : GeoPosition {
  latitude: 43,
  longitude: 34,
}

Fijaos que debo de cumplir el contrato que me dice mi interfaz GeoPosition, no solamente las propiedades tienen que ser de los tipos que he especificado aquí arriba, si no que tienen que existir las dos propiedades, si quito alguna de ellas o las comento me da un error missing in type latitude... me dice que le falta la propiedad longitude

De igual modo si pretendo añadir una propiedad nueva (por ejemplo altitude) me dice que no existe en mi contrato y me salta un error

Anidamiento interfaces

Algo muy interesante de los interfaces es que se pueden anidar, esto es algo que se usa a menudo.

Vamos a crear un interfaz Truck (camión), le voy a añadir un propiedad id string y otra location que va a ser de tipo GeoPosition (el interfaz que definimos arriba).

interface GeoPosition {
  latitude: number;
  longitude: number;
}

+ interface Truck {
+    id: string;
+    location : GeoPosition;
+ }

Podríamos crearnos ahora un variable truck de tipo Truck y le decimos que va a tener un campo id, por ejemplo un numero de matrícula, como este campo hereda de Truck hasta que yo no le diga location me da un error, y al poner el location podemos o bien asignalr la variable pos que definimos anteriormente (que de tipo GeoPosition):

const truck: Truck = {
  id: "MA389476",
  location: pos,
};

O asignarle directamente un nuevo objeto que sea de tipo GeoPosition.

const truck: Truck = {
  id: "MA389476",
  location: {
    latitude: 43,
    longitude: 34,
  },
};

Opcional y sólo lectura

Existen dos modificadores muy interesantes para las propiedades de un interfaz, que las vamos a ver a continuación, esto son el opcional y el read only.

Veámoslo con un ejemplo, supongamos que tenemos un interfaz libro tiene dos propiedades un ISBN (un número) y un autor (un string), vamos a crear dos libros de ejemplo:

interface Book {
  isbn: number;
  author: string;
}

const book1: Book = {
  isbn: 394738,
};

Si te fijas se nos ha olvidado poner la propiedad autor, con lo que TypeScript se queja, es obligatorio que informemos esa propiedad, ¿Que pasa si un libro es anónimo y no tiene autor? En ese caso podríamos meter una entrada para autor que fuera campo en blanco, o "anónimo", pero también nos podríamos plantear que directamente no tuviéramos que informar la propiedad author ¿Cómo podemos hacer esto? Podemos marcar en la definición del interface Book la propiedad author con el sufijo ?, de esta manera estamos indicando que es opcional:

interface Book {
  isbn: number;
-  author: string;
+  author?: string;
}

const book1: Book = {
  isbn: 394738,
};

Ahora el compilador de TypeScript no se nos queja, y podemos tener tanto entradas con campo autor informado, y no informado:

const book1: Book = {
  isbn: 394738,
};

+ const book2: Book = {
+  isbn: 348972,
+  author: 'Alan Doe'
+ };

Ambos libros cumplen con el contrato Book que hemos definido, ya que author es opcional y no tiene que ser incluído forzosamente.

De igual modo existe otro modificador interesante que es ReadOnly, vamos a suponer que nuestro ISBN queremos que sea de sólo lectura, es decir que una vez asignado el valor inicial, esté no se pueda modificar, para ello marcamos la propiedad isbn como readOnly:

interface Book {
-  isbn: number;
+  readonly isbn: number;
  author?: string;
}

Si ahora intentamos cambiar la propiedad ISBN a una instancia de un libro nos dará un fallo, ésta no se puede reasignar:

ISBN Read Only

Extensión

Una característica muy importante de los interfaces es su capacidad de extensión, esto lo hace muy potente, supongamos que tenemos un interfaz Soldier que va a a tener un tipo de espada swordType y un interfaz ranger que va a tener el tipo de munición y cuanta lleva consigo.

interface Soldier {
  swordType: string
}

interface Range: {
  ammoType: string;
  totalAmmo: number;
}

Vamos a crear un interfaz nuevo que vamos a llamar Ninja que va a tener como armamento bombas de humo (smokebombs) esto va a ser de tipo number, podemos crear una instancia de ninja que sea de tipo ninja y que cumpla esa interfaz.

interface Ninja {
  smokeBombs: number;
}

const ninja: Ninja = {
  smokeBombs: 3,
};

Hasta aquí todo estupendo, pero que pasaría si queremos que Ninja fuera también un soldado o un ranger... en lugar de repetir las propidades en la definicíon de la interfaz Ninja, lo que puedo hacer directamente es extender, decirle que Ninja extiende de Soldier:

- interface Ninja {
+ interface Ninja extends Soldier {
  smokeBombs: number;
}

En cuanto hago eso, fíjate que TypeScript nos avisa de que a nuestra instancia de Ninja le faltan las propiedad swordType:

En cuanto Ninja hereda de Soldier la instancia neesita la propiedad swordType

Esa propiedad la está extendiendo del interfaz soldier por eso la pone como obligatoria de informar, así pues podríamos arreglarlo tecleando lo siguiente:

const ninja: Ninja = {
+ swordType: 'katana',
  smokeBombs: 3,
};

Pero es más, puedo hacer extensión múltiple, puedo decir que un Ninja a parte de un Soldier también es un Ranger:

- interface Ninja extends Soldier {
+ interface Ninja extends Soldier, Ranger {
  smokeBombs: number;
}

Cuando hago esto, la instancia de ninja además de necesitar que le informes swordType y smokeBomb tambíen nos pide ahora las propiedades de Ranger: ammoType y totalAmmo:

const ninja: Ninja = {
  swordType: 'katana',
  smokeBombs: 3,
+ ammoType: "shuriken",
+ totalAmmo: 100,
};

La extensión en los interfaces es un buen mecanismo para reutilizar código y evitar repetirnos, es una forma de componer interfaces a partir de otros que ya existen.

¿Te apuntas a nuestro máster?

Si te ha gustado este ejemplo y tienes ganas de aprender Front End guiado por un grupo de profesionales ¿Por qué no te apuntas a nuestro Máster Front End Online Lemoncode? Tenemos tanto edición de convocatoria con clases en vivo, como edición continua con mentorización, para que puedas ir a tu ritmo y aprender mucho.