Aprendiendo TypeScript [Javascript] #3

En esta entrega tocaremos el tema de funciones y genéricos. Como se usan en TypeScript y que nos brinda para trabajar con ellos. Nuevamente utilizaremos VS Code en algunos ejemplos. Tocaremos el tema de funciones y genéricos. Como se usan en TypeScript y que nos brinda para trabajar con ellos. Nuevamente utilizaremos VS Code en algunos ejemplos.

Funciones

Tenemos 2 formas de declarar funciones. Una es de forma anónima la otra es por medio de un nombre. Esto es igual que en JavaScript no tenemos diferencias:

// por Nombre
function GetSum(x, y) {

return x + y;

}

// funcion anonima
let sum = function(x, y) { return x+y; };

Ahora veamos el enfoque con los tipos de TypesCript como debería ser declarada la misma función para tener la validación de tipos.

// por Nombre
function GetSum(x: number, y:number): number {
 return x + y;
}

// funcion anonima
let sum = function(x: number, y: number): number { return x+y; };

En el ejemplo declaramos que parámetros recibiremos en la función. Van a ser de tipo numérico y que a su vez esta función nos devolverá también un tipo de dato numérico.

Inferencias de tipos

Un tema interesante es la inferencia de tipos. Lo que hace el compilador es detectar que tipo valor le estamos agregando, por ejemplo:

let x = 1;
let x: number = 1;

Al asignar un valor numérico automáticamente sabe de qué tipo es y no es necesario usar la forma larga como vemos en la segunda línea.

Vamos a ver un ejemplo de una matriz. En una matriz el compilador decidirá cuál es el mejor tipo:

let x = [”Fernando”, “Pepe”, null];

¿Y qué pasa con las clases? veamos el ejemplo:

class herramienta{}
class Martillo extends herramienta{}
class Destornillador extends herramienta{}
class Lijadora extends herramienta{}

let list = [ new Martillo(), new Destornillador(), new Lijadora() ][/code]

El compilador tomará como tipo el primero de la lista del array. En estos casos debemos declararle de que tipo.

let list: Herramienta[] = [ new Martillo(), new Destornillador(), new Lijadora() ]

Tipos Genéricos

El objetivo de los tipos genéricos es generar clases altamente reutilizables. Podemos crear piezas de software que pueden accionar sobre una gran cantidad de tipos comunes. Esto permite que cualquier persona que lo utilice junto a sus tipos personales. Hoy la gran mayoría de los lenguajes de programación lo soportan y TypeScript no podía quedarse afuera.

Para empezar veamos un ejemplo que:

function GetValue(arg: number): number {
return arg;
}

Supongamos que en lugar de usar number usamos any, sería una forma de hacerla genérica, pero en verdad no es conveniente, estaríamos perdiendo información útil del tipo y sus funcionalidades. Ahora bien, cómo debemos declararlo de forma genérica:

function GetValue<T>(arg: T): T{
 return arg;
}

En el ejemplo vemos que agregamos la variable T que a diferencia de any será más precisa sin perder ninguna información del tipo. Ahora que tenemos nuestra declaración vemos como debemos invocar.

let result = GetValue<string>("hola mundo");

le dice que el parámetro que vamos a pasar es de ese tipo. También podemos llamar de la forma más simple y el compilador infiere el tipo automáticamente siendo tal vez un poco más legible.

let result = GetValue("hola mundo");

Ahora bien,r que problema vemos en el siguiente ejemplo:

function GetValue<T>(arg: T): T{
 return arg.length;
}

Si pasamos el tipo string no habrá ningún problema, pero si pasamos del tipo numeric no posee la propiedad length y nos dará un error.

En cambio, por ejemplo, si trabajamos con matrices esta funcionalidad, length estará presente en todos los tipos.

function GetValue<T>(arg: T[]): T[]{
 return arg.length;
}

// Otra forma de declarar
function GetValue<T>(arg: Array <T>): Array<T>{
 return arg.length;
}

Ahora vemos como hacer una clase genérica:

class GenericClass<T>
   add(arg1: T, arg2: T) => T
}

let myGenericClass = new GenericClass<numeric>();
myGenericClass.add = function(arg1, arg2) {
 return arg1 + arg2;
}

Por último, vamos a hacer limitaciones en los tipos genéricos. Como vimos en el ejemplo al hacer una clase genérica puede ser que el tipo que pasamos no tenga una funcionalidad como en el caso que devolvía length.

Lo que podemos hacer es restringir una función para trabajar con todos los tipos tengan o no la propiedad length.

Para esto necesitamos crear una interface:

interface WithLength {
 length: number;
}

function GetValue<T extends WithLength>(arg: T): T {
    console.log(arg.length);
    return arg;
}

Como podemos ver en la imagen ya no tendremos el problema ya que no tira errores.

Namespaces

Muchos lenguajes de programación como C#, Java y TypeScript nos brindan una forma de organizar nuestro codigo por medio de Namespaces. Usaremos estos namespaces para agrupar funcionalidades similares.

Supongamos que tenemos varias clases validadoras como en el ejemplo:

interface StringValidator {
isAcceptable(s: string): boolean;
}

let lettersRegexp = /^[A-Za-z]+$/;
let numberRegexp = /^[0-9]+$/;

class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}

class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 &amp;amp;&amp;amp; numberRegexp.test(s);
}
}

A medida que nuestra aplicación crezca tal vez es necesario agregar más validadores. Es un buena práctica tener nuestros validadores organizados en un Namespace. Por ahora, no le prestemos atención a la palabra export lo veremos más adelante.

namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}

const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;

export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}

export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 &amp;amp;&amp;amp; numberRegexp.test(s);
}
}
}

Supongamos que no queremos hacer que todas nuestras validaciones estén el mismo archivo. TypeScript nos permite separarlos y hacer referencia al archivo donde se encuentra el namespace base

Validation.ts

[code language="javascript"]
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}

LettersOnlyValidator.ts

/// reference path="Validation.ts" //
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}

ZipCodeValidator.ts

/// reference path="Validation.ts"
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 &amp;amp;&amp;amp; numberRegexp.test(s);
}
}
}

TestExamaple.ts

/// &amp;lt;reference path="Validation.ts" /&amp;gt;
/// &amp;lt;reference path="LettersOnlyValidator.ts" /&amp;gt;
/// &amp;lt;reference path="ZipCodeValidator.ts" /&amp;gt;

let strings = ["Hello", "98052", "101"];

let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

for (let s of strings) {
for (let name in validators) {
console.log(""" + s + "" " + (validators[name].isAcceptable(s) ? " matches " : " does not match ") + name);
}
}

Módulos

Cada módulo tiene su propio ámbito, esto significa que todo su contenido, clases, variables, funciones, etc declaradas dentro no serán visibles al menos que se exporten. Es aquí la importancia de la palabra export, export dará visibilidad a las clases que quieran usarla y para ser usados deben ser importados.

Los módulos pueden importarse entre sí por medio de un cargador de módulos. En nuestro caso estamos usando CommonJS pero podemos utilizar cualquier otro. Veamos un ejemplo supongamos que tenemos una clase que era un módulo:

customer.ts

class Customer{

let name: string;
GetName(){
return name;
}
}

export { Customer };

Simplemente por tener la palabra export ya se convertirá en un módulo. Export está diciendo que Customar podrá ser consumida o utilizada fuera del ámbito del propio módulo.

Para no exponer el nombre original podemos exportarlo como un alias de la siguiente manera:

export { Customer as PrincipalCustomer };

Ahora para utilizar en nuestra app el módulo Customer debemos importarlo de esta manera:

import{ Customer} from "./customer";

let myCustomer = new Customer();

Muchos pensaran “le fatla e .ts”. No es necesario ya que por default sabrá que se refiere a ese tipo de extensión. Supongamos que nuestro módulo posee más de una clase, y queremos importarlo en una variable

import * as list from "./customer";

let myCustomer = new list.Customer();

Una buena opción es exportarlo por default, cada módulo puede tener una y solo una exportación por default.

customer.ts

class Customer{
...
}

export default Customer;

app.ts

import Customer from "./customer";
let myCustomer = new Customer();

Realmente el uso de módulos es bastante sencillo. El tema es más extenso pero con esto nos alcanza para tener un excelente punto de partida.

Decoradores

Los decoradores vienen de Angular (2) y fueron solicitados por el equipo de Google. Si, aunque no lo crean, el equipo de Microsoft y el equipo de Google trabajaron juntos para poder implementarlo en TypeScript.

Antes que nada, para poder hacer uso de los decorator, debemos cambiar una configuración en nuestro tsconfig y agregar lo lo siguiente.

{
"compilerOptions": {
"target": "es5",  'ESNEXT'. */
"module": "commonjs",
"experimentalDecorators": true
}
}

aunque suene raro que diga experimental, en muchos lugares ya se usa en produccion asi que no debemos preocuparnos por esto.

Un decorador es un tipo especial de declaración que adjunta metadata o añadir una cierta característica que no deberían cambiar el comportamiento a una clase, método, propiedades o parámetros.

Para hacer un decorator debemos cumplir algunas especificaciones dependiendo de a cual queramos afectar.

type ClassDecorator = <Function extends Function>(target: TFunction: TFunction | void;
type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPrpoertyDescriptor<T>): void;
Type PropertyDecorator = (target: Object, propertyKey: string | symbol): void;
Type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number): void;

Para mejor su funcionamiento vamos a hacer un ejemplo donde tendremos una clase log que irá registrando el comportamiento de una clase.

function logClass(constructor: Function){
console.log(Date.now() + " - LogClass......: " + constructor["name"]);
}

function logMethod(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor&amp;lt;T&amp;gt;):void{
console.log(Date.now() + " - LogMethod.....: " + propertyKey.toString());
}

function logParameter(target: Object, propertyKey: string | symbol, parameterIndex: number):void{
console.log(Date.now() + " - LogParamter...: " + propertyKey.toString());
}

Lo que podemos ver en nuestra clase es que tenemos 3 funciones: una para class, otra para un method y por último para un parámetro. Ahora veamos cómo se vería nuestra clase decorada:

@logClass
class Calculadora{

constructor(){}

@logMethod
Sumar(arg1: number, @logParameter arg2: number){
return arg1 + arg2;
}

}

let calc = new Calculadora();
console.log(calc.Sumar(1,2));

El símbolo @ le dirá compilador que es un decorador.  Para ejecutar nuestro codigo primero debemos compilarlo con tsc y luego lo ejecutaremos con nodeJs desde la consola de VS Code.

tsc decorator.ts
node decorator.js

Conclusión

Hemos visto el uso funciones, la inferencia de tipos y una introducción a tipos genéricos. Si bien es simplemente una introducción es un buen punto de partida para comenzar a entrar más en profundidad. Por otro laod, hemos visto el uso  namespaces, módulos y decoradores. Nuevamente es simplemente una introducción y un buen punto de partida para comenzar a entrar más en profundidad. En los próximos post veremos un ejemplo de una aplicación sencilla, un abm, utilizando TypeScript junto a JQuery.

0 0 votos
Valora la Publicación
Suscribirse
Notificación de
guest
0 Comentarios
Feedback en línea
Ver todos los Comentarios

Comentarios Recientes

0
Nos encantaría conocer tu opinión: ¡comenta!x
Ir a la barra de herramientas