Introducción a TypeScript – Parte 2


Después de hacer una pequeña a introducción a Typescript en el anterior artículo. En este analizaremos algunas características que lo hacen un lenguaje interesante para aquellos profesionales que se dedican al desarrollo Web.

Explicaremos cuatro características principales del lenguaje.

  • Interfaces.
  • Clases.
  • Herencia
  • Módulos.

Interfaces en TypeScript

Una interfaz es otra de las formas en las que TypeScript comprueba los errores durante la compilación. Es como un “plano de una casa” (blueprint) para un objeto.  Las interfaces son muy prácticas ya que nos permiten decirle al compilador de TypeScript exactamente que debe esperar del objeto que definamos.

Por tanto, cuando declaramos una interfaz, se deben definir los tipos de sus propiedades para asegurar que el objeto solo aceptará la definición de los mismos. Además, podemos emplear variables opcionales que se pueden declarar mediante el signo de interrogación (?) después del nombre de la variable.

Ejemplo TypeScript

// interfaces.ts
interface Persona {  
  nombre : string;
  edad?: number;
}

function presentarPersona(persona : Persona) {  
  console.log('Te presento a: ' + persona.nombre);
  console.log('Que tiene : ' + persona.edad + " años");
}
presentarPersona({ nombre: 'Salvador', edad: 40 });

En este ejemplo, hemos creado una interfaz simple, Persona que acepta dos variables: una cadena de texto llamada nombre y un número “opcional” edad.

Después hemos definido una función que imprime los atributos nombre y edad por consola, y finalmente la hemos ejecutado pasando los parámetros a la función. Como veis un ejemplo muy sencillo que ilustra la definición de una interfaz.

Ahora si compilamos el archivo, se crea el código JavaScript necesario para que lo entienda el navegador, en este paso el compilador elimina la declaración de la interfaz y simplemente pasa el objeto a la función presentarPersona().

Código generado JavaScript

function presentarPersona(persona) {
    console.log('Te presento a: ' + persona.nombre);
    console.log('Que tiene : ' + persona.edad + " años");
}
presentarPersona({ nombre: 'Salvador', edad: 40 });
presentarPersona({ edad: 40 });

Si ejecutamos este archivo en línea de comandos, veremos como se devuelven los datos de la persona tal y cómo hemos definido en la función.

Clases en TypeScript

Las clases son muy parecidas a nivel funcional a las interfaces, ya que también constituyen un modelo o contrato para un objeto, pero son mucho más potentes que las interfaces. Las clases tienen constructores, que permiten crear de forma sencilla nuevas instancias de objetos utilizando la palabra reservada new.

Esta funcionalidad permite realizar un enfoque más orientado a objetos ya que la sintaxis se asemeja mucho a los lenguajes Java y C#. Las clases también se pueden utilizar en ECMAScript 6 usando el compilador de Babel para compilarlas al JavaScript que entienden los navegadores actuales.

Las propiedades de una clase no pueden ser declaradas opcionales, como sí se puede hacer con las interfaces. Pero las clases admiten la posibilidad de implementar interfaces dentro de sí mismas con propiedades opcionales.

Ejemplo TypeScript

// clases.ts
class Refresco {  
  nombreCliente: string;
  precio : string;
  constructor(nombreCliente : string, precio : string) {
    this.nombreCliente = nombreCliente;
    this.precio = precio;
  }
  comprarRefresco() {
    console.log('Hola, ' + this.nombreCliente + '! Aquí está su refresco. Son ' + this.precio + ', por favor!');
  }
}

let sergio = new Refresco('Sergio', '2.50€');  
sergio.comprarRefresco();  

En este ejemplo de una clase, hemos creado un objeto llamado Refresco y declarado dos variables de tipo cadena de texto para el nombreCliente y el precio. La clase también tiene un constructor para crear la instancia del objeto Refresco y configurar los atributos recibiéndolos como parámetros del constructor.

También hemos definido que la clase contenga un método llamado comprarRefresco(), que se ejecuta en tiempo de ejecución y simplemente imprime un pequeño mensaje para decirle al cliente el precio del refresco que ha comprado. Así que el objeto de clase y las variables declaradas se compilan en una función vacía en Javascript, ya que esto no afecta en tiempo de ejecución.

Código generado JavaScript

// clases.ts
var Refresco = (function () {
    function Refresco(nombreCliente, precio) {
        this.nombreCliente = nombreCliente;
        this.precio = precio;
    }
    Refresco.prototype.comprarRefresco = function () {
        console.log('Hola, ' + this.nombreCliente + '! Aquí está su refresco. Son ' + this.precio + ', por favor!');
    };
    return Refresco;
}());
var peter = new Refresco('Peter', '2.50€');
peter.comprarRefresco();

Como se observa en el ejemplo, el código Javascript es menos legible por el uso de funciones y prototipos. Además el constructor se suprime y se crea una función con los parámetros.

Si ejecutamos el JavaScript compilado, veremos cómo se devuelve el mensaje.

Ejecución en consola

$ node clases.js
Hola, Peter! Aquí está su refresco. Son 2.50€, por favor!

Herencia en TypeScript

Siguiendo con la orientación a objetos, las clases pueden extender su funcionalidad a otras clases utilizando la herencia. Esto significa que podemos crear nuevas clases basadas en otras existentes, como las clases Futbolista y Nadador heredan de la clase Deportista. Por consiguiente, las clases hijas heredaran automáticamente todas las variables, constructores y métodos de la clase padre y pueden ser anulados o ampliados en la clases hijas.

Para ilustrarlo utilizaremos el ejemplo anterior y vamos a refactorizar la clase original Refresco, y generaremos dos clases hijas RefrescoLigh y RefrescoZero a las cuales les configuraremos precios diferentes.

Ejemplo TypeScript

// herencia.ts
class Refresco {  
  nombreCliente: string;
  constructor(nombreCliente : string) {
    this.nombreCliente = nombreCliente;
  }
  comprarRefresco(precio : string = '2.50€') {
    console.log('Hola, ' + this.nombreCliente + 
        '! Aquí está su refresco. Son ' + precio + ', por favor!');
  }
}

class RefrescoLigh extends Refresco {  
  constructor(nombreCliente : string) {
    super(nombreCliente);
  }
  comprarRefresco(precio = '2.75€') {
    super.comprarRefresco(precio);
  }
}
class RefrescoZero extends Refresco {  
  constructor(nombreCliente : string) {
    super(nombreCliente);
  }
  comprarRefresco(precio = '2.90€') {
    super.comprarRefresco(precio);
  }
}

let pedido1 = new RefrescoLigh('Juanjo').comprarRefresco();  
let pedido2 = new RefrescoZero('Jose Carlos').comprarRefresco();

Es este ejemplo hemos ampliado la funcionalidad de la clase Refresco y sobrescrito la función comprarRefresco(). Como vemos, las clases hijas no heredan automáticamente las llamadas al constructor de la clase padre sino que es necesario llamarlo usando la función super(). También utilizamos super() en el método comprarRefresco() y hemos cambiado el precio, pero queremos que la función se ejecute de la misma manera que antes, sin añadir parámetros.

Código generado JavaScript

var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
// herencia.ts
var Refresco = (function () {
    function Refresco(nombreCliente) {
        this.nombreCliente = nombreCliente;
    }
    Refresco.prototype.comprarRefresco = function (precio) {
        if (precio === void 0) { precio = '2.50€'; }
        console.log('Hola, ' + this.nombreCliente +
            '! Aquí está su refresco. Son ' + precio + ', por favor!');
    };
    return Refresco;
}());
var RefrescoLigh = (function (_super) {
    __extends(RefrescoLigh, _super);
    function RefrescoLigh(nombreCliente) {
        return _super.call(this, nombreCliente) || this;
    }
    RefrescoLigh.prototype.comprarRefresco = function (precio) {
        if (precio === void 0) { precio = '2.75€'; }
        _super.prototype.comprarRefresco.call(this, precio);
    };
    return RefrescoLigh;
}(Refresco));
var RefrescoZero = (function (_super) {
    __extends(RefrescoZero, _super);
    function RefrescoZero(nombreCliente) {
        return _super.call(this, nombreCliente) || this;
    }
    RefrescoZero.prototype.comprarRefresco = function (precio) {
        if (precio === void 0) { precio = '2.90€'; }
        _super.prototype.comprarRefresco.call(this, precio);
    };
    return RefrescoZero;
}(Refresco));
var order1 = new RefrescoLigh('Juanjo').comprarRefresco();
var order2 = new RefrescoZero('Jose Carlos').comprarRefresco();

Ahora, si compilamos y ejecutamos este archivo, observamos en la salida estándar los dos pedidos distintos con diferentes precios para cada tipo de refresco. Como observamos con el uso de TypeScript nos ahorramos mucho código Javascript y la complejidad asociada.

Ejecución en consola

Hola, Juanjo! Aquí está su refresco. Son 2.75€, por favor!
Hola, Jose Carlos! Aquí está su refresco. Son 2.90€, por favor!

Módulos en TypeScript

En ECMAScript 6 y en muchos frameworks de JavaScript actuales, existe el concepto de módulo, el cual está compartido en TypeScript.

Los módulos, son básicamente, una forma de compartir código entre archivos, lo que permite estructurar mejor la funcionalidades en los proyectos. Este enfoque ayuda a mantener los ficheros en un tamaño razonable y organizar la dependencias entre ellos fácilmente. De tal forma, que cuando encaremos un proyecto grande, podamos tener las distintas funcionalidades separadas en carpetas.

Los módulos se crean en su propio ámbito de aplicación, lo que significa que ninguna de las variables, funciones o clases creadas dentro del módulo pueden accederse externamente, a menos que se añadan con la palabra clave export.

Ejemplo TypeScript

// refresco.ts
module TiendaRefresco {
    export class Refresco {
        nombreCliente: string;
        constructor(nombreCliente: string) {
            this.nombreCliente = nombreCliente;
        }
        comprarRefresco(precio: string = '2.50€') {
            console.log('Hola, ' + this.nombreCliente + '! Aquí está su refresco. Son ' + precio + ', por favor!');
        }
    }
}

Así pues, hemos creado un módulo simple llamado TiendaRefresco, utilizando el mismo ejemplo de antes. La clase Refresco la hemos incluido usando la palabra export para permitir que se pueda visualizar externamente.

Código generado JavaScript

// refresco.ts
var TiendaRefresco;
(function (TiendaRefresco) {
    var Refresco = (function () {
        function Refresco(nombreCliente) {
            this.nombreCliente = nombreCliente;
        }
        Refresco.prototype.comprarRefresco = function (precio) {
            if (precio === void 0) { precio = '2.50€'; }
            console.log('Hola, ' + this.nombreCliente + '! Aquí está su refresco. Son ' + precio + ', por favor!');
        };
        return Refresco;
    }());
    TiendaRefresco.Refresco = Refresco;
})(TiendaRefresco || (TiendaRefresco = {}));

Ejecución en consola

// principal.ts
/// <reference path="refresco.ts" />

let order1 = new TiendaRefresco.Refresco('Daniel').comprarRefresco(); 

En otro archivo, principal.ts, podemos llamar a la clase del módulo TiendaRefresco para ejecutar el código, pero primero hay que importar el módulo en el archivo. Esto se puede hacer mediante el uso de una referencia como se observa en el ejemplo, para buscar el archivo en el que se definió el módulo. A continuación, simplemente llamamos a la función.

$ tsc *.ts --outFile principal.js
$ node principal.js
Hola, Daniel! Aquí está su refresco. Son 2.50€, por favor!

Para que sea más fácil de ejecutar, podemos utilizar el compilador de TypeScript para combinar estos archivos en un solo módulo. Se puede usar el parámetro --outFile, para especificar el nombre de la ubicación de salida. Una vez generado el archivo JavaScript, se ejecuta, y la clase se comporta como se esperaba.

Este es un ejemplo muy sencillo de utilización de módulos, su uso puede ser más complejo, por lo que te recomiendo que eches un vistazo a la documentación de los módulos en la web oficial de TypeScript.

Resumen

Espero que este artículo te sirva como punto de partida en el aprendizaje de las características de TypeScript . A continuación te dejo algunos recursos que te servirán para profundizar en el conocimiento de TypeScript

Más información | TypeScriptLang
En la web | Introducción a TypeScript – Parte 1

Deja un comentario