TypeScript
Introducción
1. Objetivos
JavaScript es particularmente maleable como hemos visto, pero hace necesario un esfuerzo de programación para obtener funcionalidades básicas (manejo de herencia, sobrecarga, etc.). Estas características son comunes en lenguajes actuales como Java, C#, Python, etc. y puede parecer desafortunado que JavaScript (ECMAScript 5) no haya evolucionado al mismo ritmo. Microsoft ha desarrollado un superconjunto de JavaScript llamado TypeScript. Cuando hablamos de superconjunto, significa que JavaScript está incluido en TypeScript, que agrega lo que parece faltar en el lenguaje. Una de sus ventajas es la etapa de compilación de TypeScript, que consiste en obtener código JavaScript compatible con el navegador.
Las últimas versiones de JavaScript (desde ECMAScript 2015) reflejan estas evoluciones. Por lo tanto, se deberían generalizar a medida que los motores de JavaScript se actualizan en los navegadores.
2. Hello world
Partiremos de la forma más sencilla en JavaScript para realizar nuestro primer ejemplo. La idea es obtener el compilador TypeScript a través del administrador de paquetes de Node.js (NPM).
Si no tiene la herramienta NPM, puede descargarla del sitio web de nodejs.org (https://nodejs.org/en/download/).
El siguiente comando se utiliza para instalar el compilador TypeScript:
npm install -g typescript
Ahora crearemos un archivo TypeScript (extensión...
Variables y constantes
1. Variable
Hasta ahora hemos declarado nuestras variables con la palabra clave var, para limitar el alcance a la función actual. A diferencia de los lenguajes habituales, no existía un ámbito intermedio entre el espacio global y el espacio de la función. TypeScript proporciona la palabra clave let, que se usa como var con la diferencia de que el alcance de la variable se limita al bloque actual y no a la función en su conjunto.
ejemplo2.ts
function tabla( max ) {
let i = 1;
for ( ;i<=max;i++ ) {
for ( let j = 1;j<=max;j++ ) {
console.log( i + "*" + j + "=" + ( i * j ) );
}
}
}
tabla(7);
El compilador tsc construye al final el siguiente archivo:
function tabla(max) {
var i = 1;
for (; i <= max; i++) {
for (var j = 1; j <= max; j++) {
console.log(i + "*" + j + "=" + (i * j));
}
}
}
tabla(7);
Por lo tanto, solo convirtió...
Tipado
1. Declaración
a. Variable
Al declarar una variable, constante o incluso un parámetro, todo lo que tiene que hacer es declarar el tipo después del nombre.
function tabla(max:number) {
for ( let i:number=0; i<=max; i++ ) {
for ( let j:number=0; j <=max; j++ ) {
let mensaje:string = `${i}*${j}=${i*j}`;
console.log( mensaje );
}
}
}
tabla(8);
En este ejemplo, hemos declarado un parámetro max y dos variables i y j como números. Una variable mensaje es una cadena.
Si no se respeta el tipado en la compilación, por ejemplo, con tabla("8"); el compilador nos lo indica con:
error TS2345: Argument of type '"8"' is not assignable to parameter
of type 'number'.
b. Funciones
El tipo devuelto por una función se define después de la declaración. Si la función no devuelve un valor, lo indicamos con void.
function max(...params:number[]):number {
let m:number=0;
for (let i:number=0; i < params.length; i++ ) {
m = Math.max(params[i],m);
}
return m;
}
alert( "Max =" + max(3,2,6));
Aquí tenemos una función max que devuelve un número. Como argumento, tenemos un número variable de parámetros gracias a los tres puntos. Para recuperar los valores, pasamos por un tipo de "tabla de números" para este caso.
2. Cadenas
El tipo string designa la cadena. Hay dos formatos de cadena:...
Clases
1. Declaración y uso
Hasta ahora, hemos visto que una clase en realidad se reduce a una función "constructora" y que el objeto prototype se usa para poner en común los métodos de nuestra clase. Funciona, pero no es muy natural para un desarrollador de objetos. TypeScript introduce para este propósito, al igual que con la última versión de JavaScript (ECMAScript 6), una palabra clave class.
En el siguiente ejemplo, hemos construido una clase que representa a una persona, con los campos apellido y nombre. El constructor se llama explícitamente constructor.
class Persona {
nombre:string;
apellido:string;
constructor(nombre:string,apellido:string) {
this.nombre = nombre;
this.apellido = apellido;
}
hola() {
alert( "Hola " + this.apellido + " " + this.nombre );
}
}
let persona:Persona = new Persona("Alexandre", "Brillant" );
persona.hola();
La creación de una instancia se hace de manera clásica con el operador new.
Tenga en cuenta que el acceso al campo se debe realizar obligatoriamente a través de la palabra clave this, mientras que generalmente está implícito en los lenguajes orientados a objetos.
2. Herencia
La herencia también se obtiene naturalmente con la palabra clave extends.
class Empleado extends Persona {
job:string;
constructor(nombre:string,apellido:string,job:string) {
super(nombre,apellido);
this.job = job;
}
hola() {
super.hola();
alert( "Usted es " + this.job );
}
}
let persona:Empleado = new Empleado( “Alexandre”, “Brillant”,
"Autor" );
persona.hola();
Aquí hemos heredado de la clase Persona. Gracias a la palabra clave super, tenemos la posibilidad de reutilizar el constructor o un método de la clase padre.
El compilador no permite un constructor...
Interfaces
1. Declaración
La interfaz le permite crear tipos adicionales dedicados a los objetos y a las funciones. Esto se utilizará para comprobar que los objetos utilizados tienen determinadas características.
Por ejemplo, queremos un tipo ConNombre que haga referencia a los objetos con un campo nombre, que es el caso de nuestra clase Persona. La clase puede tener otros campos, no importa mientras tenga al menos el campo nombre.
interface ConNombre {
nombre:string;
}
class Persona {
nombre:string;
apellido:string;
public constructor(nombre:string,apellido:string) {
this.nombre = nombre;
this.apellido = apellido;
}
}
class Producto {
nombre:string;
public constructor(nombre:string) {
this.nombre = nombre;
}
}
let persona:ConNombre = new Persona( “alexandre”, “brillant” );
let producto:ConNombre = new Producto( "hal" );
alert( producto.nombre );
Nuestras variables persona y producto, aunque representan dos clases Persona y Producto independientes, están tipadas de la misma manera con la interfaz ConNombre.
2. Propiedades opcionales y en modo solo lectura
Puede suceder que algunas propiedades aún no estén definidas. En este caso, es posible indicar con un signo de interrogación en la interfaz que puede faltar una propiedad y así evitar un error de compilación.
Podríamos modificar nuestra interfaz ConNombre de la siguiente manera:
interface ConNombre {
nombre:string;
apellido?:string;
}
En este caso, el código anterior sigue funcionando porque la instancia persona tiene el miembro apellido y la instancia producto sí tiene el miembro apellido. El compilador, por otro lado, permitirá el acceso a la propiedad apellido incluso en el producto.
alerta (producto.apellido); // OK
Si invocamos otro miembro diferente a nombre y apellido, obtendremos un mensaje de error en la compilación porque nuestra interfaz no los declara.
alerta (producto.otro); // ERROR porque otro no está en
la interfaz...
Genéricos
1. Declaración y uso
Hasta ahora, hemos utilizado funciones o clases con parámetros o miembros con un tipo específico. Esto significa que no podemos reutilizar estas funciones y clases fuera de estos tipos. Sin embargo, hay casos en los que nos gustaría poder manipular nuestras funciones/clases con diferentes tipos. Esto es lo que proponen hacer los "genéricos".
function tab<T>(a:T,b:T):T[] {
let t = new Array<T>();
t.push( a );
t.push( b );
return t;
}
alert( tab<string>( "hello", "world" ) );
alert( tab<number>( 10, 20 ) );
alert( tab<boolean>( true, "false" ) ); // ERROR
En nuestro ejemplo, almacenamos dos argumentos en una tabla. Para que esto funcione con cualquier tipo, agregamos al nombre de la función tab un parámetro T entre paréntesis, correspondiente al tipo de nuestros argumentos y nuestra tabla de resultados. Durante la invocación de nuestra función, el parámetro T se reemplaza por el tipo deseado. Seguidamente, en el primer caso, tenemos una tabla de cadenas de caracteres y el segundo caso, una tabla de números. El último caso provoca un error de compilación, ya que nuestros argumentos deben ser booleanos y hemos pasado una cadena como segundo argumento.
Es posible dejar que el propio compilador encuentre el tipo adjunto a T con inferencia de tipos. Por ejemplo, si escribimos:
tab ("hello", "world");...
Módulos
1. Declaración y uso
Para diseñar un módulo, es suficiente con tener la palabra clave export o import en un archivo TypeScript. Para que una variable, función, interfaz o clase esté disponible en un módulo, se le coloca el prefijo export.
Retomemos el ejemplo de esta clase presente en el archivo persona.ts:
class Persona {
private nombre : string;
private apellido : string;
public constructor( nombre : string, apellido : string ) {
this.nombre = nombre;
this.apellido = apellido;
}
public hello():void {
alert( "Hello " + this.nombre + " " + this.apellido );
}
}
El compilador lo ha traducido en JavaScript por:
var Persona = (function () {
function Persona(nombre, apellido) {
this.nombre = nombre;
this.apellido = apellido;
}
Persona.prototype.hello = function () {
alert("Hello " + this.nombre + " " + this.apellido);
}; ...
Espacio de nombres
1. Declaración y uso
El espacio de nombres con la palabra clave namespace albergará todas las variables, funciones, interfaces y clases que tengan una agrupación lógica. Las declaraciones en el espacio de nombres permanecerán locales en el espacio de nombres. Para que las declaraciones estén disponibles fuera del espacio de nombres, deben ir precedidas de la palabra clave export. Para su uso, a un elemento exportado se le coloca el prefijo del espacio de nombres.
personas.ts:
namespace Personas {
let mensaje = "Hello";
interface PuedeDecirHola {
hello();
}
export class Persona implements PuedeDecirHola {
private nombre : string;
private apellido : string;
public constructor( nombre : string, apellido : string ) {
this.nombre = nombre;
this.apellido = apellido;
}
public hello():void {
alert( mensaje + this.nombre + " " + this.apellido );
}
}
export class Empleado extends Persona {
public constructor( nombre : string, apellido : string ) {
super( nombre, apellido );
}
}
}
main.ts:
let autor: Personas.Persona =
new Personas.Persona( “alexandre”, “brillant”...