Software Crafters ® | Creado con 🖤 para elevar el nivel de la conversación sobre programación en español| Legal
Este es el primer artículo de una serie sobre el lenguaje de programación Typescript en la que abordaremos los fundamentos de este magnifico lenguaje open source. Typescript fue desarrollado en 2012 por Anders Hejlsberg, creador de Pascal, Delphi y C#, y su equipo en Microsoft.
Esencialmente se trata de un superconjunto de Javascript, es decir, amplía Javascript con una nueva sintaxis que añade, entre otras cosas, el tipado estático opcional, genéricos, decoradores y elementos de POO como interfaces o property accessors.
TypeScript compila código JavaScript que se ejecuta en cualquier navegador, host, sistema operativo o motor de JavaScript que admita ECMAScript 3 (o más reciente).
A lo largo de los últimos años Javascript ha crecido mucho y se ha convertido en el camino a seguir para escribir aplicaciones multiplataforma (aunque no el único, sigo siendo fan de Xamarin y C#). Las aplicaciones Javascript pueden ejecutarse en todas las plataformas, ya sea en móviles, web o escritorio. Sin embargo, cuando Javascript fue creado por primera vez su proposito no fue este, sino que fue diseñado para un uso simple en aplicaciones muy pequeñas.
TypeScript trata de resolver la mayoría de los problemas con JavaScript centrándose en mejorar la experiencia y la productividad de nosotros, los desarrolladores. Nos permite utilizar técnicas como el tipado estático opcional y/o la encapsulación para generar un código mucho más mantenible y escalable que con JavaScript tradicional, sin perder el carácter dinámico del mismo.
demás estos dos últimos años la popularidad de TypeScript se ha disparado y se perfila como uno de los lenguajes de programación con más futuro.
Para poder instalar typescript el único requisito necesario es tener instalado en nuestro equipo nodejs y su administrador de paquetes (npm).
Cuando nos referimos a la "instalación de typescript" en realidad hacemos referencia a la instalación de su compilador, llamado tsc. Este se encarga de convertir el código TypeScript a Javascript con la versión ECMAScript compatible que deseemos.
Para instalarlo tan solo tenemos que ejecutar en la terminal lo siguiente:
npm install -g typescript
Para comprobar que la instalación se ha realizado correctamente ejecuta:
tsc -v
Esto nos mostrará la versión, en mi caso la 2.6
Para probar la instalación, creemos un archivo TypeScript simple llamado helloworld.ts con el siguiente código:
console.log("Hello world!")
El siguiente comando compilará un archivo
.ts
a un archivo .js
:
tsc helloworld.ts
Esto creará un fichero
helloworld.js
. Este fichero lo podemos ejecutar en el navegador o como haremos en este caso, con Node:
node helloworld.js //"Hello world"
El comando tsc se puede utilizar de formas muy variadas, veamos unos cuantos usos interesantes:
Para compilar varios archivos:
tsc hello1.ts hello2.ts hello3.ts
También podríamos utilizar asteriscos:
tsc *.ts
Esto compilará todos los ficheros typescript que se encuentren en el directorio. Cada fichero typescript se compilará en su archivo javascript correspondiente.
También podemos compilar todos los archivos TypeScript en un solo archivo JavaScript. Esto nos ayudaría a reducir el número de solicitudes HTTP que un navegador debe realizar y de esta manera mejorar el rendimiento de nuestro sitio web. Para ello utilizaremos la opción --out del compilador:
tsc *.ts --out helloworld.js
Si queremos evitar tener que compilar el archivo typescript cada vez que hagamos una modificación del mismo, podemos utilizar la opción
--watch
:
tsc *.ts --out helloworld.js --watch
Con esto conseguiremos que cada vez que guardemos las modificaciones el fichero se compilará automáticamente.
Para crear un proyecto de TypeScript, lo único que necesitamos es crear un directorio y dentro del mismo crear un archivo de configuración de Typescript tsconfig.json. Este fichero, entre otras cosas, indica al compilador qué archivos compilar, qué archivos ignorar y qué a que version de javascript transpilar (por ejemplo, ECMAScript 3).
Para crear nuestro primer proyecto de TypeScript, creemos el nuevo directorio y agreguemos el archivo de configuración de TypeScript:
mkdir myProject cd myProject touch tsconfig.json
En este articulo ejecutaré todos los ejemplos en la terminal usando Node.js. Node corre sobre Chrome V8, uno de los motores de JavaScript más actualizados disponibles. La versión 6 de Node.js se envía con una versión de Chrome V8 capaz de soportar el 95% de la especificación ECMAScript 2015, como se muestra en Node Green, mientras que la versión 8 es compatible con el 99%.
Con respecto a ECMAScript 2017, ambas versiones admiten el 23% y el 73% de las especificaciones, respectivamente. Por lo tanto, la mejor opción es configurar nuestro proyecto para que se compile en ECMAScript 2015, lo que permitirá a los usuarios con Node.js 6 y 8 ejecutar los ejemplos sin problemas.
Además de indicar la versión de javascript, también configuraremos estas otras opciones del compilador:
module
, indica a Typescript que use el formato CommonJS para los modulos.removeComments
, elimina los comentarios del código generado.sourceMap
, permite usar mapas de origen para asignar el código transpilado al código fuente.outDir
, indica el directorio en el cual se almacenará el código generado (build).include
, indica el directorio de los ficheros a compilar (src).Nuestro fichero de configuración quedaría tal que así:
`{
"compilerOptions": { "module": "commonjs", "target": "es2015", "removeComments": true, "outDir": "./build" }, "include": ["src/**/*"] }`
Las opciones utilizadas en el archivo de configuración anterior son solo un pequeño subconjunto de lo que TypeScript admite. Por ejemplo, podríamos indicar al compilador que soporte decoradores, archivos tsx, etc. En la documentación oficial tenemos una lista con todas las opciones que admite el compilador.
Ahora que entendemos cómo iniciar un proyecto y cómo configurar el compilador, estamos listos para ir profundizando en las diferentes características del lenguaje.
Sin lugar a dudas la principal característica de Typescript es su sistema de tipos, el cual realiza una formalización de los tipos de Javascript, mediante una representación estática de su sistema de tipado dinámico. Esto permite a los desarrolladores definir variables y funciones fuertemente tipadas sin perder la esencia de Javascript (su naturaleza debilmente tipada y su extremada flexibilidad). Poder definir los tipos durante el tiempo de diseño nos ayuda a evitar errores en tiempo de ejecución, como podría ser pasar el tipo de variable incorrecto a una función.
Veamos con un ejemplo de las ventajas que nos ofrece:
let myName: string = "Miguel"; let printName = (name: string) ={ console.log(name); }
Si intentamos ejecutar la función
printName()
pasándole un parámetro vacío o del tipo incorrecto el compilador nos advierte en tiempo de desarrollo, en lugar de generar excepciones en tiempo de ejecución. Además, el intellisense nos ayuda con el autocompletado sugiriendonos la variable más apropiada para pasar como parámetro a la función. Por otro lado, Typescript es lo suficientemente inteligente para inferir el tipo sin indicarselo explícitamente.
Además de los tipos String y Number, TypeScript también admite los siguientes tipos básicos:
Echemos un vistazo al siguiente codigo para obtener una vision general de lo que Typescript nos permite hacer con los tipos básicos:
// 1 - declaracion del tipo type Ranking = [number, string, boolean]; // 2 - definición de variables let position: number; let playerName: string; let finishedGame: boolean; let ranking: Ranking; let hallOfFame: Array<Ranking> = []; // 3 - crea un ranking position = 1; playerName = "Bruno Krebs"; finishedGame = true; ranking = [position, playerName, finishedGame]; hallOfFame.push(ranking); // 4 - crea otro ranking position = 2; playerName = "Maria Helena"; finishedGame = true; ranking = [position, playerName, finishedGame]; hallOfFame.push(ranking); // 5 - define una funcion que recorre todos los rankings function printRankings(rankings: Array<RankingTuple>): void { for (let ranking of rankings) { console.log(ranking); } } // 6 - llama a la función printRankings(hallOfFame);
Si quieres continuar profundizando en el sistema de tipos de typescript te recomiendo que eches un vistazo a la documentación oficial.
En typescript tenemos podemos hacer uso de dos tipos de bucles diferentes for ... in y for .. of. For... in es una proviene de versiones antiguas de javascript el cual nos permite recorrer objetos iterables obteniendo sus indices. En cambio, For...of es una caracteristica introducida en ES6, la cual nos permite recorrer colecciones obteniendo su valor, veamos las diferencias con un ejemplo:
let list = [4, 5, 6]; for (let i in list) { console.log(i); // "0", "1", "2", } for (let i of list) { console.log(i); // "4", "5", "6" }
Otra de las características de Typescript es heredada de ECMAScript 2015 la posibilidad de crear módulos, loc cuales no son más que una forma de encapsular código en su propio ámbito. Nos permiten agrupar nuestro código en diferentes ficheros, permitiéndonos exportarlos y utilizarlos donde los necesitemos. Esto nos facilita la tarea de crear software más ordenado y por ende más escalable y mantenible.
Continuando con el ejemplo de la sección anterior, si quisieramos exportar el tipo Ranking y la función printRankings(), tan solo tendríamos que añadirle la palabra reservada export antes de la definición de los mismos:
export type RankingTuple = [number, string, boolean]; export function printRankings(rankings: Array<RankingTuple>): void { for (let ranking of rankings) { console.log(ranking); } }
Para importarlos en otro fichero tan solo nos bastaría con lo siguiente:
import {RankingTuple, printRankings} from './myRankingModule.ts';
Un objeto es una entidad que agrupa un estado y una funcionalidad relacionada,una clase, no es más que una plantilla genérica a partir de la cuál instanciamos los objetos. Dicho de otra manera, una clase es una abstracción en la que se define el comportamiento que va a tener el objeto.
Las clases en Typescript son muy similares a lo que nos ofrecen otros lenguajes de orientación a objetos tradicionales, esto nos ayudará a modularizar nuestro código y a simplificar el desarrollo. Veamos con un ejemplo como definir una clase:
class Employee { //atributo accesible desde fuera de la clase public name : string; //atributos accesible desde clases que hereden de Employee protected age: number; //access only inside the Employee class private mobile : string; constructor(name:string ,age:number, mobile:string){ this.name = name; this.age = age; this.mobile = mobile; } getName(){ return this.name; } setName(name:string){ this.name = name; } getAge(){ return this.age; } setAge(age:number){ this.age = age; } getMobile(mobile:string){ this.mobile = mobile; } }
Como sabemos, los métodos get y set, también conocidos como métodos accesores, son simples funciones que usamos en las clases para mostrar (get) o modificar (set) el valor de un atributo. Normalmente los escribimos como "get" o "set" seguido del nombre de la propiedad a la que queremos acceder, por ejemplo: getName(). En Typescript se puede hacer de esta forma o haciendo uso de las palabras reservadas get o set delante del nombre de la función:
class Actor { private _name : string; constructor(_name:string){ this._name = _name; } set name (value: string){ this._name = value; } get name (){ return this._name; } } let actor = new Actor('Haider Malik'); console.log(actor.name); //set actor.name = 'Jane Doe'; console.log(actor.name);
Otro elemento fundamental en la OO es la herencia, la cual nos permite extender la funcionalidad nuestra clase herendando de una clase padre. La clase hija hereda todos los miembros de su clase base y puede sobreescribir todos aquellos métodos y/o propiedades públicos o protegidos.
class Manager extends Employee { constructor(name : string, age: number , mobile : string){ super(name,age,mobile); this.age = 24; } } let manager = new Manager(‘Jane’,23, ‘0343–23332233’); console.log(manager.getName()); console.log(manager.getAge());
Este tipo de clases no pueden ser instanciadas ya que se usan para definir comportamientos independientemente de su concreción. Su implementación en typescript es similar a la de una clase normal con la diferencia que hay que anteponer el termino abstract antes de declararlas.
abstract class Product { productName : string = "Default"; price :number = 1000; abstract changeName(name: string): void; calcPrice(){ return this.price; } } class Mobile extends Product { changeName(name : string) : void { this.productName = name; } } let mobProduct = new Mobile(); console.log(mobProduct); mobProduct.changeName("Super It Product"); console.log(mobProduct);
Las interfaces son abstracciones que definen el comportamiento de las clases que la implementan. Son muy prácticas ya que nos permiten decirle al compilador cual es el comportamiento que debe esperar del objeto que definamos. Como ocurre en otros lenguajes de programación, TypeScript no requiere que un objeto tenga exactamente la misma estructura definida por la interfaz. Para que se los considere válidos, los objetos pueden tener cualquier forma siempre que definan las funciones y propiedades requeridas por la interfaz que implementan. Veamos un pequeño ejemplo de como declararla e implementarla:
interface ICar{ engine: string; color: string; brake: () => void; } class Car implements ICar { constructor (public engine: string, public color: string) { } function brake(){ console.log("Frenando...") } }
Los decoradores son un estándar propuesto en ECMAScript2016. En Typescript podemos habilitarlos añadiendo a nuestro tsconfig.json la directiva "experimentalDecorators: true".
{ "compilerOptions": { "module": "commonjs", "target": "es2015", "removeComments": true, "experimentalDecorators": true, "outDir": "./build" }, "include": ["src/**/*"] }
Por si no lo sabías, los decoradores son un mecanismo para modificar clases, métodos, propiedades e incluso parámetros de forma declarativa. Obviaré los decoradores de propiedades y de parámetros ya que en la practica no resultan demasiado interesantes.
Un decorador de clase es una función que recibe y devuelve el método constructor de la clase que "decora". Veamos un ejemplo de implementación:
const log = (originalConstructor: new(...args: any[]) => T) => { function newConstructor(... args) { console.log("Argumentos: ", args.join(", ")); new originalConstructor(args); } newConstructor.prototype = originalConstructor.prototype; return newConstructor; } @log class Person { constructor(name: string, age: number) {} } new Person("Miguel", 33); //Argumentos: Miguel, 33
Como podemos observar el decorador log reemplaza el constructor de la clase por una función que, en primer lugar, registra a través de la consola los argumentos y luego devuelve el constructor original.
El decorador de métodos es una función que acepta 3 argumentos: el objeto sobre el que se define el método, la clave de la propiedad y un descriptor de propiedad:
const log = (target: Object, key: string | symbol, descriptor: TypedPropertyDescriptor<Function>) => { return { value: function( ... args: any[]) { console.log("Arguments: ", args.join(", ")); const result = descriptor.value.apply(target, args); console.log("Result: ", result); return result; } } } class Calculator { @log add(x: number, y: number) { return x + y; } } new Calculator().add(1, 3); //Arguments: 1, 3 //Result: 4
En este ejemplo, el decorador log reemplaza la función original por una nueva que registra los argumentos recibidos, ejecuta el método original, almacena el resultado en una variable local lo registra en la consola y finalmente lo devuelve.
En este articulo he tratado de mostrar la mayoría de características básicas de Typescript. Aunque he condensado bastante información en este artículo, me he dejado muchísimos elementos en el tintero. La idea es que esta entrada sirva como base a futuras publicaciones relacionadas con este magnifica lenguaje.
Espero haber facilitado tu transición a Typescript, si ya lo conocías espero que el artículo te sirva como referencia. Si te ha gustado la entrada valora y comparte en tus redes sociales. No dudes en comentar dudas, aportes o sugerencias, estaré encantado de responder.
Este artículo se distribuye bajo una Licencia Creative Commons Reconocimiento-CompartirIgual 4.0 Internacional (CC BY-SA 4.0)