Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
TypeScript: comodidad con JavaScript para los desarrolladores de .NET
Sin duda ha invertido mucho en Microsoft .NET Framework, una importante plataforma que incluye muchas herramientas disponibles. Si combina su conocimiento de C# o Visual Basic .NET con XAML, el mercado para sus habilidades podría parecer casi infinito. Hoy, sin embargo, debe tomar en cuenta un lenguaje que se ha establecido por algún tiempo y que, en los últimos años, ha tenido mucho éxito como la plataforma de aplicaciones para estar al corriente. Por supuesto que me refiero a JavaScript. El crecimiento y la capacidad de las aplicaciones de JavaScript son inmensos. Node.js, una plataforma completa para desarrollar aplicaciones de JavaScript escalables, se ha vuelto muy popular e incluso se puede implementar en Windows Azure. Más aun, es posible usar JavaScript con HTML5, para el desarrollo de juegos, aplicaciones móviles e incluso en aplicaciones para la Tienda Windows.
Como desarrollador .NET, no puede pasar por alto las capacidades de JavaScript, ni tampoco su extensión en el mercado. Cuando declaro esto a mis colegas, por lo general escucho quejas sobre lo difícil que es trabajar con JavaScript, que no hay tipificación fuerte ni estructuras de clase. Mi respuesta ante este debate es que JavaScript es un lenguaje funcional y que cuenta con patrones para realizar lo que se desee.
Es aquí donde TypeScript entra en juego. TypeScript no es un lenguaje nuevo. Es un superconjunto de JavaScript, un eficaz superconjunto tipado, que significa que todas las funciones de JavaScript son válidas en TypeScript y que lo producido por el compilador es JavaScript. TypeScript es un proyecto de código abierto y toda la información relacionada con el proyecto se puede encontrar en typescriptlang.org. En el momento de escribir este artículo, TypeScript estaba en la versión preliminar 0.8.1.
En este artículo, abarcaré los conceptos básicos de TypeScript en forma de clases, módulos y tipos para demostrar que un desarrollador de .NET se puede familiarizar con un proyecto de JavaScript.
Clases
Si trabaja con lenguajes como C# o Visual Basic .NET, las clases son un concepto conocido para usted. En JavaScript, las clases y la herencia se logran mediante patrones, como cierres y prototipos. TypeScript presenta la sintaxis de tipo clásico a la que está acostumbrado y el compilador produce el JavaScript que logra la intención. Observe el siguiente fragmento de JavaScript:
var car;
car.wheels = 4;
car.doors = 4;
Se ve simple y sencillo. Sin embargo, los desarrolladores de .NET han dudado en decidirse por JavaScript debido a su enfoque flexible en cuanto a la definición de objeto. Se pueden agregar otras propiedades al objeto "car" (automóvil), sin cumplimiento y sin saber qué tipo de datos representa cada una y, por consiguiente, generar excepciones durante el tiempo de ejecución. ¿Cómo cambia esto la definición del modelo de clases de TypeScript y cómo heredamos y expandimos el automóvil? Considere el ejemplo de la figura 1.
Figura 1: objetos en TypeScript y en JavaScript
TypeScript | JavaScript |
class Auto{ wheels; doors;}var car = new Auto();car.wheels = 2;car.doors = 4; |
var Auto = (function () { function Auto() { } return Auto;})();var car = new Auto();car.wheels = 2;car.doors = 4; |
A la izquierda hay un objeto de clase bien definido, llamado automóvil, con las propiedades de "wheels" (ruedas) y "doors" (puertas). A la derecha, el JavaScript producido por el compilador de TypeScript es casi el mismo. La única diferencia es la variable Auto.
En el editor de TypeScript, no puede agregar otras propiedades sin recibir una advertencia. No puede simplemente comenzar con una declaración como car.trunk = 1. El compilador se quejará diciendo "No existe la propiedad trunk en Auto", lo que es una bendición para todo el que haya tenido que localizar este problema, debido a la flexibilidad de JavaScript o según su perspectiva, la "pereza" de JavaScript.
Los constructores, aunque están disponibles en JavaScript, se volvieron a mejorar con las herramientas de TypeScript al exigir la creación del objeto durante el tiempo de compilación y al no permitir que se cree sin transferir los elementos y tipos correctos en la llamada.
No solo puede agregar el constructor a la clase, sino que también puede hacer que los parámetros sean opcionales, establecer un valor predeterminado o usar accesos directos en la declaración de propiedad. Veamos tres ejemplos que muestran la posible eficacia de TypeScript.
La figura 2 muestra el primer ejemplo, un constructor simple en el que se inicializó la clase al transferir los parámetros de ruedas y puertas (representados por w y d respectivamente). El JavaScript generado (a la derecha) es casi equivalente, pero a medida que la dinámica y las necesidades de su aplicación se expanden, ese no será siempre el caso.
Figura 2: un constructor simple
TypeScript | JavaScript |
class Auto{ wheels; doors; constructor(w, d){ this.wheels = w; this.doors = d; }}var car = new Auto(2, 4); |
var Auto = (function () { function Auto(w, d) { this.wheels = w; this.doors = d; } return Auto;})();var car = new Auto(2, 4); |
En la figura 3, modifiqué el código de la figura 2, dejando el parámetro de las ruedas (w) predeterminado en 4 y el parámetro de las puertas (d) como opcional, al insertar un signo de interrogación a su derecha. Observe, como en el ejemplo anterior, que el patrón de configuración de la propiedad de la instancia a los argumentos es una práctica común que usa la palabra clave "this".
Figura 3: un constructor simple, modificado
TypeScript | JavaScript |
class Auto{ wheels; doors; constructor(w = 4, d?){ this.wheels = w; this.doors = d; }}var car = new Auto(); |
var Auto = (function () { function Auto(w, d) { this.wheels = w; this.doors = d; } return Auto;})();var car = new Auto(4, 2); |
Esta es una de las características que me gusta ver en los lenguajes .NET: ser capaz de simplemente agregar la palabra clave pública antes del nombre del parámetro en el constructor para declarar la propiedad de la clase. La palabra clave privada está disponible y cumple la misma declaración automática, pero oculta la propiedad de la clase.
Los valores predeterminados, parámetros opcionales y las anotaciones de tipo están extendidas con la característica de declaración automática de propiedad de TypeScript, lo que lo convierte en un método fácil y le ayuda a ser más productivo. Compare el script de la figura 4 y podrá ver las diferencias en complejidad que surgen.
Figura 4: la característica de declaración automática
TypeScript | JavaScript |
class Auto{ constructor(public wheels = 4, public doors?){ }}var car = new Auto();car.doors = 2; |
var Auto = (function () { function Auto(wheels, doors) { if (typeof wheels === "undefined") { wheels = 4; } this.wheels = wheels; this.doors = doors; } return Auto;})();var car = new Auto();car.doors = 2; |
Las clases en TypeScript también ofrecen herencia. Si nos quedamos con el ejemplo del automóvil, puede crear una clase Motorcycle (Motocicleta) que extienda la clase inicial. En la figura 5, también agregué las funciones de conducir y detenerse a la clase base. Agregar la clase Motorcycle, que se hereda de Auto y establece las propiedades adecuadas para "doors" (d) y "wheels" (w), se logra con pocas líneas de código en TypeScript.
Figura 5: agregar la clase Motorcycle
class Auto{
constructor(public mph = 0,
public wheels = 4,
public doors?){
}
drive(speed){
this.mph += speed;
}
stop(){
this.mph = 0;
}
}
class Motorcycle extends Auto
{
doors = 0;
wheels = 2;
}
var bike = new Motorcycle();
Algo importante de mencionar es que en la parte superior del JavaScript producido por el compilador, verá una pequeña función llamada " ____ extends", como se ve en la figura 6, que es el único código insertado en el JavaScript resultante. Esta es una clase auxiliar que ayuda en la funcionalidad de herencia. Como nota aparte, esta función auxiliar tiene la misma firma sin importar la fuente, así que si está organizando su JavaScript en varios archivos y usa una utilidad como SquishIt o Web Essentials para combinar los scripts, es posible que reciba un error, en función de cómo la utilidad rectifique las funciones duplicadas.
Figura 6: el JavaScript producido por el compilador
var __extends = this.__extends || function (d, b) {
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
}
var Auto = (function () {
function Auto(mph, wheels, doors) {
if (typeof mph === "undefined") { mph = 0; }
if (typeof wheels === "undefined") { wheels = 4; }
this.mph = mph;
this.wheels = wheels;
this.doors = doors;
}
Auto.prototype.drive = function (speed) {
this.mph += speed;
};
Auto.prototype.stop = function () {
this.mph = 0;
};
return Auto;
})();
var Motorcycle = (function (_super) {
__extends(Motorcycle, _super);
function Motorcycle() {
_super.apply(this, arguments);
this.doors = 0;
this.wheels = 2;
}
return Motorcycle;
})(Auto);
var bike = new Motorcycle();
Módulos
Los módulos en TypeScript son el equivalente a los espacios de nombres en .NET Framework. Es una excelente manera de organizar su código y para encapsular reglas y procesos de negocios, lo que no sería posible sin esta funcionalidad (JavaScript no cuenta con una manera integrada para ofrecer esta función). El patrón de módulo o los espacios de nombres dinámicos, como en JQuery, es el patrón más común para espacios de nombres en JavaScript. Los módulos de TypeScript simplifican la sintaxis y producen el mismo efecto. En el ejemplo de Auto, puede recopilar el código en un módulo y exponer solo la clase Motorcycle, como se muestra en la figura 7.
El módulo de ejemplo encapsula la clase base y se expone la clase Motorcycle al anteponer la palabra clave exportada. Esto permite que se cree una instancia de Motorcycle y que se usen todos sus métodos, pero la clase Auto base permanece oculta.
Figure 7: encapsular la clase Auto en un módulo
module Example {
class Auto{
constructor(public mph : number = 0,
public wheels = 4,
public doors?){
}
drive(speed){
this.mph += speed;
}
stop(){
this.mph = 0;
}
}
export class Motorcycle extends Auto
{
doors = 0;
wheels = 2;
}
}
var bike = new Example.Motorcycle();
Otro excelente beneficio de los módulos es que los puede combinar. Si crea otro módulo llamado Ejemplo, TypeScript supone que se puede acceder al código del primer módulo y al código del nuevo módulo mediante afirmaciones de Ejemplo, tal como en los espacios de nombres.
Los módulos facilitan el mantenimiento y la organización de su código. Gracias a ellos, sostener aplicaciones a gran escala se vuelve más fácil para los equipos de desarrollo.
Tipos
La falta de la seguridad de los tipos es una de las grandes quejas de los desarrolladores, que no navegan las aguas de JavaScript todos los días. Pero la seguridad de los tipos está disponible en TypeScript (es por eso que se llama TypeScript) y va más allá de solo declarar una variable como una cadena o booleano.
En JavaScript, la práctica de asignar foo a x y más adelante asignar 11 a x en el código es perfectamente aceptable, pero podría volverlo loco cuando intente descifrar por qué obtiene el siempre presente NaN durante el tiempo de ejecución.
La característica de seguridad de tipos es una de las mayores ventajas de TypeScript y hay cuatro tipos inherentes: "string" (cadena), "number" (número), "bool" (bool) y "any" (cualquiera). La figura 8 muestra la sintaxis para declarar el tipo de variable s e IntelliSense que proporciona el compilador, una vez que sabe qué acciones puede realizar según el tipo.
Figura 8: un ejemplo de TypeScript IntelliSense
Aparte de permitir la escritura de una variable o función, TypeScript tiene la capacidad de inferir tipos. Puede crear una función que simplemente devuelva una cadena. Con eso en mente, el compilador y las herramientas proporcionan la inferencia de tipos y muestran automáticamente las operaciones que se pueden realizar de vuelta, como puede ver en la figura 9.
Figura 9: un ejemplo de inferencia de tipos
El beneficio aquí es que puede ver que lo que se devuelve es una cadena, sin tener que adivinar. La inferencia de tipos es una gran ayuda cuando se trata de trabajar con otras bibliotecas, a los que los desarrolladores hacen referencia en su código, como JQuery o incluso con Document Object Model (DOM).
Otra forma de aprovechar el sistema de tipos es mediante anotaciones. En retrospectiva, la clase Auto original se declaró solo con ruedas (wheels) y puertas (doors). Ahora, mediante anotaciones, podemos asegurarnos de que los tipos adecuados estén establecidos al crear la instancia de Auto del automóvil (car):
class Auto{
wheels : number;
doors : number;
}
var car = new Auto();
car.doors = 4;
car.wheels = 4;
Sin embargo, en el JavaScript que se produjo, el compilador elimina las anotaciones, así que no hay dependencias grandes o adicionales de las que preocuparse. Una vez más el beneficio es la tipificación fuerte y, además, la eliminación de errores simples que por lo general se encuentran durante el tiempo de ejecución.
Las interfaces proporcionan otro ejemplo del tipo de seguridad que ofrece TypeScript. Las interfaces le permiten definir la forma de un objeto. En la figura 10, se ha agregado un nuevo método a la clase Auto, llamado travel, que acepta un parámetro con un tipo de Trip (Viaje).
Figura 10: la interfaz Trip
interface Trip{
destination : string;
when: any;
}
class Auto{
wheels : number;
doors : number;
travel(t : Trip) {
//..
}
}
var car = new Auto();
car.doors = 4;
car.wheels = 4;
car.travel({destination: "anywhere", when: "now"});
Si intenta llamar al método travel con algo más que no sea la estructura correcta, el compilador de la fase de diseño le da un error. Por otra parte, si ingresa este código en JavaScript, digamos en un archivo .js, lo más probable es que no reciba este tipo de error hasta que ejecute la aplicación.
En la figura 11, puede ver que aprovechar las anotaciones de tipo ayuda no solo al desarrollador inicial, sino también al desarrollador posterior, que debe mantener la fuente.
Figura 11: las anotaciones ayudan a mantener su código
Código y bibliotecas existentes
Entonces, ¿qué sucede con su código JavaScript existente o qué ocurre si le encanta compilar a partir de Node.js o usar bibliotecas como toastr, Knockout o JQuery? TypeScript tiene archivos de declaración para ayudar. Primero, recuerde que todas las funciones de JavaScript son válidas en TypeScript. Así que, si tiene algo propio, puede copiar ese código directo en el diseñador y el compilador producirá el JavaScript uno a uno. La mejor opción es crear su propio archivo de declaración.
Para las bibliotecas y marcos importantes, un señor llamado Boris Yankov (twitter.com/borisyankov en Twitter) ha creado un buen repositorio en GitHub (github.com/borisyankov/DefinitelyTyped) que tiene varios archivos de declaración para algunas de las bibliotecas de JavaScript más populares. Esto es precisamente lo que el equipo de TypeScript esperaba que sucediera. Por cierto, el equipo de TypeScript creó el archivo de declaración Node.js y está disponible como parte del código fuente.
Creación de un archivo de declaración
Si no puede ubicar el archivo de declaración para su biblioteca o si está trabajando con su propio código, deberá crear un archivo de declaración. Se comienza al copiar el JavaScript al lado del TypeScript y al agregar las definiciones de tipos y luego usar la herramienta de línea de comandos para generar el archivo de definición (*.d.ts) al que hacer referencia.
La figura 12 muestra un script sencillo para calcular la puntuación media en JavaScript. Copié el script al lado izquierdo del editor, agregué las anotaciones para los tipos y guardaré el archivo con la extensión .ts.
Figura 12: creación de un archivo de declaración
TypeScript | JavaScript |
function gradeAverage(grades : string[]) { var total = 0; var g = null; var i = -1; for(i = 0; i < grades.length; i++) { g = grades[i]; total += getPointEquiv(grades[i]); } var avg = total / grades.length; return getLetterGrade(Math.round(avg));}function getPointEquiv(grade : string) { var res; switch(grade) { case "A": { res = 4; break; } case "B": { res = 3; break; } case "C": { res = 2; break; } case "D": { res = 1; break; } case "F": { res = 0; break; } } return res;}function getLetterGrade(score : number) { if(score < 1) { return "F"; } if(score > 3) { return "A"; } if(score > 2 && score < 4) { return "B"; } if(score >= 1 && score <= 2) { return "C"; } if(score > 0 && score < 2) { return "D"; }} |
function gradeAverage(grades){ var total = 0; var g = null; var i = -1; for(i = 0; i < grades.length; i++) { g = grades[i]; total += getPointEquiv(grades[i]); } var avg = total / grades.length; return getLetterGrade(Math.round(avg));}function getPointEquiv(grade) { var res; switch(grade) { case "A": { res = 4; break; } case "B": { res = 3; break; } case "C": { res = 2; break; } case "D": { res = 1; break; } case "F": { res = 0; break; } } return res;}function getLetterGrade(score) { if(score < 1) { return "F"; } if(score > 3) { return "A"; } if(score > 2 && score < 4) { return "B"; } if(score >= 1 && score <= 2) { return "C"; } if(score > 0 && score < 2) { return "D"; }} |
Luego, abriré un símbolo del sistema y usaré la herramienta de línea de comandos de TypeScript para crear el archivo de definición y el JavaScript resultante:
tsc c:\gradeAverage.ts –declarations
El compilador crea dos archivos: gradeAverage.d.ts es el archivo de declaración y gradeAverage.js es el archivo JavaScript. En cualquier futuro archivo de TypeScript que necesite la funcionalidad gradeAverage, solo agrego una referencia en la parte superior del editor, como esta:
/// <reference path="gradeAverage.d.ts">
Toda la escritura y las herramientas se resaltan cuando hace referencia a esta biblioteca y ese es el caso con cualquier biblioteca importante que puede encontrar en el repositorio DefinitelyTyped GitHub.
Una excelente característica que aporta el compilador a los archivos de declaración es la capacidad de recorrer automáticamente las referencias. Esto significa que si hace referencia a un archivo de declaración para jQueryUI, que a su vez hace referencia a jQuery, su archivo TypeScript actual se beneficiará de la finalización de instrucciones y verá las firmas de función y tipos tal como si hubiera hecho referencia directamente a jQuery. También puede crear un archivo único de declaración, por ejemplo "myRef.d.ts", que haga referencia a todas las bibliotecas que pretende usar en su solución y después hacer una referencia única en su código TypeScript.
Windows 8 y TypeScript
Con HTML5, un ciudadano de primera clase en el desarrollo de aplicaciones de la Tienda Windows, los desarrolladores se han preguntado si se puede usar TypeScript con este tipo de aplicaciones. La respuesta breve es sí, pero necesita cierta configuración para hacerlo. A la fecha de este artículo, las herramientas disponibles ya sea mediante Visual Studio Installer u otras extensiones no han habilitado las plantillas por completo, dentro de la aplicación de la Tienda Windows de JavaScript en Visual Studio 2012.
Hay tres archivos de declaración clave disponibles en el código fuente en typescript.codeplex.com: winjs.d.ts, winrt.d.ts y lib.d.ts. Hacer referencia a estos archivos le dará acceso a las bibliotecas WinJS y WinRT de JavaScript, las que se usan en este entorno para acceder a la cámara, a los recursos del sistema, etcétera. También puede agregar referencias a jQuery, para obtener las características de IntelliSense y de seguridad de los tipos mencionadas con anterioridad.
La figura 13 es un ejemplo breve que muestra el uso de estas bibliotecas para acceder a la ubicación geográfica del usuario y rellenar la clase Location (Ubicación). A continuación, el código crea una etiqueta de imagen HTML y agrega un mapa estático de la API de Mapas de Bing.
Figura 13: archivos de declaración para Windows 8
/// <reference path="winjs.d.ts" />
/// <reference path="winrt.d.ts" />
/// <reference path="jquery.d.ts" />
module Data {
class Location {
longitude: any;
latitude: any;
url: string;
retrieved: string;
}
var locator = new Windows.Devices.Geolocation.Geolocator();
locator.getGeopositionAsync().then(function (pos) {
var myLoc = new Location();
myLoc.latitude = pos.coordinate.latitude;
myLoc.longitude = pos.coordinate.longitude;
myLoc.retrieved = Date.now.toString();
myLoc.url = "http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/"
+ myLoc.latitude + "," + myLoc.longitude
+ "15?mapSize=500,500&pp=47.620495,-122.34931;21;AA&pp="
+ myLoc.latitude + "," + myLoc.longitude
+ ";;AB&pp=" + myLoc.latitude + "," + myLoc.longitude
+ ";22&key=BingMapsKey";
var img = document.createElement("img");
img.setAttribute("src", myLoc.url);
img.setAttribute("style", "height:500px;width:500px;");
var p = $("p");
p.append(img);
});
};
En resumen
Las características que TypeScript agrega al desarrollo de JavaScript son pequeñas, pero generan grandes beneficios para los desarrolladores de .NET, que están familiarizados con características similares en los lenguajes que usan para el desarrollo habitual de aplicaciones de Windows.
TypeScript no es una solución mágica y no pretende serlo. Pero para el que tenga dudas sobre usar JavaScript, TypeScript es un excelente lenguaje que puede facilitar el proceso.
Shayne Boyer es un MVP de Telerik, miembro destacado de los desarrolladores de Nokia, MCP, orador de INETA y arquitecto de soluciones en Orlando, Florida. Ha estado desarrollando soluciones basadas en Microsoft durante los últimos 15 años. En los últimos 10 años, ha trabajado en aplicaciones web a gran escala, centrado en la productividad y el rendimiento. En su tiempo libre, Boyer dirige el Grupo de usuarios de Windows Phone y Windows 8 en Orlando y escribe un blog sobre lo último en tecnología en tattoocoder.com.
Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Christopher Bennage