jueves, 7 de junio de 2018

TypeScript: ¿Cómo escribir código front-end sin arrancarse los pelos?

Todos los que trabajamos desde hace ya varios años en el desarrollo de back-ends, estamos acostumbrados a desarrollar en .NET disfrutando las bondades de un lenguaje orientado a objetos y tipado como es C#. Nos esforzamos por hacer código prolijo, mantenible, modularizable y altamente testeable, por lo cual nuestra aplicación de back-end es performante, robusta y escalable. Sin embargo, ahora nos enfrentamos a un nuevo desafío: nos toca, además, desarrollar código de front-end. 

Hoy en día el mercado le da mucho valor a aquellos que llamaremos Full Stack Developers y, si queremos convertirnos en tales, debemos aprender otras tecnologías y lenguajes como HTML, JavaScript, NodeJs, entre otros. Entonces, ¿qué pasa con todas esas cualidades a las que estamos acostumbrados cuando cambiamos C# por JavaScript?

En C# solemos usar tipos de datos tales como string, int y bool. Si declaramos una variable, sabemos que la misma siempre contendrá un dato de ese tipo. Instanciamos objetos que comparten atributos y comportamientos; declaramos clases que nos definen cómo construir cada uno de esos objetos; usamos interfaces para definir un contrato a implementar; tenemos conceptos como la herencia y la composición para vincular los objetos; podemos definir fácilmente si algo es público o privado y ordenar nuestras implementaciones en namespaces.

Cuando empezamos a programar en JavaScript, asignamos un número entero a una variable. Unas líneas de código más tarde, le asignamos por error un string. Nada nos alerta de ningún problema y nuestro código llega a la aplicación funcionando, pero al presionar el botón, nada sucede. Sorprendidos, miramos la consola del browser y encontramos unas intimidantes letras rojas: “NaN”. El código quiso realizar una operación con el string esperando que fuera un número.

Una vez que encontramos la asignación errónea y la arreglamos, seguimos adelante. Escribimos una función que recibe dos parámetros (por ejemplo: “unaFuncion(a, b)”) y la olvidamos varias líneas después. Llamamos a la función olvidada y le pasamos un argumento (“unaFuncion(a)”). Guardamos el progreso, vamos a la aplicación web y aparece la frase “b is not defined”. Esto se debe a que en JavaScript, una función puede ser llamada con cualquier cantidad de argumentos. Si tiene de más, usa los que necesita, y, si se le pasan menos, toma el resto como undefined.

Luego creamos una “clase” (en realidad, JavaScript no conoce el concepto de clase) mediante una función, más o menos así:

function Objeto(argumento1, argumento2){

propiedad1 = argumento1;
propiedad2 = argumento2;

funcion1 = function(){
        return argumento1 + argumento2;
    }
}

También creamos dos objetos con esta clase. Uno haciendo Objeto (1,2) y el otro haciendo Objeto (3,4). De esta forma, cuando ejecutemos nuestro código y evaluemos al primer objeto, descubriremos que su objeto.Metodo1() devuelve 7 (3 + 4), no 3. ¿Qué ocurrió?  

Las propiedades de los objetos fueron creadas en lo que se llama el scope global. Allí, son accesibles desde cualquier función o código que esté corriendo en la página web. Es decir, ambos objetos estaban apuntando a las mismas variables. Y no sólo eso, sino que cualquier otro objeto puede llamar a la función “Funcion1”. En JavaScript, una función puede acceder sólo a las funciones y variables que estén declaradas en sí mismas, las funciones que la engloban, o el scope global.

Modificamos la función Objeto, agregamos var delante de cada propiedad y función para hacerlas privadas y volvemos a ejecutar el código. En este momento nos aparece un mensaje diciendo “objeto.Funcion1 is not defined”. Ahora la función “Funcion1” no es visible hacia afuera. Esto lo podemos arreglar escribiendo “this.” delante de la misma. 

Una función no puede acceder al scope de otra declarada dentro, salvo que tenga una referencia a la otra función y que la propiedad o función a la que queremos acceder esté declarada como “this.funcion”.

Supongamos que queremos aplicar herencia de un objeto a otro. Primero tenemos que crear la “clase” padre en una expresión de función Inmediatamente Invocada (IIFE en inglés) en el scope global:

var Vehiculo = (function()
{
    function Vehiculo (año, modelo, fabricante)
    {
        this.modelo = modelo;
        this.Año = año;
        this.fabricante = fabricante;
    };
    Vehiculo.prototype.retornarInfo() = function()
    {
        return this.año + ' ' + this.fabricante + ' ' + this.modelo;
    };
    return Vehiculo;
})()

Para implementar la clase hija, agregándole propiedades y funciones propias, debemos hacer lo siguiente:

var Auto = (function (padre)
{
    Auto.prototype = new Vehiculo();
    Auto.prototype.constructor = Auto;
    function Auto (año, modelo, fabricante)
    {
        parent.call(this, año, modelo, fabricante);
        this.cantidadDeRuedas = 4;
    };
    Auto.prototype.retornarInfo = function ()
    {
        return 'Tipo de Vehiculo: Auto ' + parent.prototype.retornarInfo.call(this);
    }
    return Auto;
})(Vehiculo)

TypeScript surge para solucionar estos inconvenientes, y otros tantos más que no hemos enumerado.

TypeScript es un lenguaje tipado, es decir, podemos decirle a una variable, o a los parámetros de una función, que deben aceptar un solo tipo de dato. Hacer esto es tan fácil como:

function helloWorld(persona : string)
{
    return "Hello " + (persona || "world") + "!";
}

También es un lenguaje que hay que compilar antes de usar. Esto nos facilitará la tarea de encontrar variables mal asignadas en tiempo de compilación, antes de poner el código a funcionar en la página o aplicación. Además, nos avisa cuando una función es llamada con un número de argumentos distinto al original.

En TypeScript sí se pueden crear clases e interfaces. Esto nos brinda un grado de abstracción mayor y el poder implementar herencia o composición de una forma mucho más cómoda.

Las interfaces se implementan así:

interface Persona 
{
    Nombre : string;
    Apellido : string;
}

De ahí podemos llamar a la interface “Persona” en una función así:

function nombrePersona (persona : Persona)
{
    return persona.Nombre;
}

Y crear las clases de esta forma:

class Persona
{
    Nombre : string;
    Apellido : string;
    constructor (unNombre : string, unApellido : string)
    {
        this.Nombre = unNombre;
        this.Apellido = unApellido;
    }

    retornarNombre()
    {
        return Nombre + '' + Apellido;
    }
};

TypeScript tiene los modificadores public, private y protected, que en esta clase no fueron usados. El lenguaje interpretará las funciones y propiedades como públicas por defecto.

Para implementar una interfaz sobre una clase, simplemente debemos escribir:

class Estudiante implements Persona
{
    ...
}

Y para implementar herencia:

class Estudiante extends Persona
{
    retornarNombre()
    {
        super.retornarNombre() + " el estudiante";
    }
}

Con estos ejemplos podemos notar que TypeScript hace menos engorroso el programar en front-end, brindándonos herramientas que ya conocemos para hacerlo. Si sólo desarrollamos en back-end, este lenguaje hace que nuestros primeros pasos para convertirnos en desarrolladores full-stack sean mucho menos amargos.

Autor:
Tec. Daniel Panelo
.NET Developer