Typescript Function Types, Arrow Functions y Decorators
Edison Sanchez
Posted on June 28, 2020
Continuando con nuestro intenso estudio de Typescript vamos a evaluar ahora la parte de Function Types para ver cómo podemos definir correctamente un Callback, y arrow functions en Typescript. Si te has perdido algo de lo anterior puedes revisar en mi perfil los diferentes posts.
Supongamos que tenemos una función que recibe como argumento otro método, function o callback; lo primero es siempre tratar de evitar los tipos any; por lo que si el callback se llama por ejemplo cb tendríamos lo siguiente:
// Defino un tipo para error que sera un argumento en el callback.
type FunctionCallBackError = Error | null;
// Defino la estructura de la función que debe llegar en el argumento con nombre CallBack
// Esto es un Tipo Function o Function Type. Recibira dos argumentos, y no retornara nada.
type FunctionCallBack = (error: FunctionCallBackError, response: Object) => void ;
//Llamó a los Types dentro de una función principal, donde tenemos como argumento
//el callback, y de igual manera si no es null el callback hará el llamado con los argumentos
//que deben corresponder al tipo segun la definicion arriba.
function FuncionPrincipal ( funcionCallback : FunctionCallBack) : void {
if(funcionCallback)
funcionCallback(null, { code: '00' });
}
Arrow Functions
Finalizando este ejemplo llevamos esto como una Arrow Function definiendo la estructura de esta función como un tipo a su vez, de una manera similar a como se definió FunctionCallback:
//Definimos la función con sus argumentos y retorno
type FuncionArrow = (funcionCallBack: FunctionCallBack) => void;
//Llamaremos a nuestra función como un arrow function.
const ArrowFunction: FuncionArrow = (callbackFunc: FunctionCallBack): void => {
if(callbackFunc)
callbackFunc(null, { code: '00' })
}
Decorators
Esto es muy utilizado en Angular, es un poco complicado; pero permite extender funcionalidad de algunos elementos. En el siguiente ejemplo haremos una funcion la cual ejecutara un "console.log", dicha funcion la usaremos como un decorator del metodo getModel dentro de la clase Vehiculo; cuando se llame a este metodo se aplicara tambien el decorador. Los decoradores como funciones tienen dos argumentos: target, y key, estos en el caso de las clases corresponden a el nombre de la clase y el nombre del metodo respectivamente.
//Definimos la funcion que vamos ausar como decorator.
function Historial(target, key) {
console.log('Function: ' + key);
}
//Definimos la clase.
class Vehiculo {
model: string;
constructor(model: string) {
this.model = model;
}
//Empleamos el decorador Historial para la funcion getModel, por lo que cada
//vez que se ejecute este metodo se va ejecutar el decorador.
@Historial
getModel() {
console.log(this.model);
return this.model;
}
}
//Creamos un Objeto con la clase Vehiculo, y llamamos el método getModel
const Carro: Vehiculo = new Vehiculo('Tucson');
Carro.getModel();
Esto tendría una salida en consola asi:
Funcion: getModel
Tucson
Decoradores de Clases
Vimos en el ejemplo arriba que el argumento target no fue empleado; este corresponde a la clase y nos permite con decoradores hacer algunas modificaciones directamente a las clases, la funcionalidad es similar, pero el decorador extiende la clase que llega como parámetro "target" y modifica la misma:
//Definimos la función como Init, la cual extenderá Target que es una clase
function init(target) {
return class extends target {
marca = 'Hyundai';
model = super.getModel(); //Podemos traer de la clase original.
color = 'Azul';
getModel() {
return `${this.marca} ${this.model} ${this.color}`;
}
};
}
//Definimos la clase con el Decorador
@init
class NuevoVehiculo {
model: string;
constructor(model: string) {
this.model = model;
}
getModel() {
console.log(this.model);
return this.model;
}
}
//Creamos un Objeto con la clase Vehiculo, y llamamos el método getModel
const NuevoCarro: NuevoVehiculo = new NuevoVehiculo('Tucson');
NuevoCarro.getModel();
//Esto traera Hyundai Tucson Azul.
Decorators de Propiedades
También los decoradores pueden ser definidos para que una propiedad dentro de una clase; para el ejemplo supongamos que cuando hagamos un set en el campo anio (year) el decorador le quitara un año. Es decir, que en un primer escenario al crear el objeto le quitara un año a lo enviado en el constructor, y si cambio el valor entonces a 2030, el valor que tendra sera de 2019 al aplicar el decorador:
//Definimos la función que sera el decorator que efectuara un cambio del año.
function newAnio(target, key) {
let _valor: number = this[key]; // Tomamos la variable en donde estará el decorador.
//Generamos los setter and Getter.
const getter = () => {
return _valor;
};
const setter = (nuevoValor: number) => {
_valor = nuevoValor - 1;
};
//Agregamos al Objeto las propiedades getter and setter
Object.defineProperty(target, key, { get: getter, set: setter });
}
class VehiculoAnio {
nombre: string;
@newAnio
anio: number;
constructor(_nombre: string, _anio: number) {
this.nombre = _nombre;
this.anio = _anio;
}
}
const Tucson2016 = new VehiculoAnio('Tucson', 2018);
Tucson2016.anio = 2030;
const FinalAnio = Tucson2016.anio;
Un punto importante aquí es la propiedad del objeto, y como adicionales el get y el set; podríamos redefinirlo pero en este caso no lo habíamos definido en la clase ya que como la propiedad anio y nombre están publicas (por default son publicas) entonces se puede acceder a ellas directamente sin ningún método. Pero para definir la propiedad se emplea en el decorador el método defineProperty que adiciona o modifica propiedades dentro de los objetos. Target seria el objeto en sí por lo cual este sería el primer argumento, la llave seria el segundo y por último la propiedad o propiedades que serán adicionadas o modificadas.
Decorators en Parametros.
Recomendado principalmente para formar la metadata dentro de un parámetro. Un decorador de parámetro tiene mas limitaciones que otros tipos de decoradores, ignoran cualquier valor retornado, ayudan a dar mas informacion con respecto a un parámetro (es requerido, es valido, etc.). El mejor uso convencional para este decorador es servir de Log de la data del parámetro. Que tener en cuenta?
Debe estar asociado a una clase.
La función que involucra al parámetro con el decorator no puede ser una función global, debe estar asociada necesariamente a una clase.
function DecoradorDeParametro(target, propertyKey, index) {
const metadataKey = `log_${propertyKey}_parameters`;
if(Array.isArray(target[metadataKey])){
target[metadataKey].push(index);
} else {
target[metadataKey] = [index];
}
console.log(target);
console.log(propertyKey);
console.log(index);
}
class TargetDemoClass {
public Metodo1(parametro1: any, @DecoradorDeParametro parametroDecorado: any) {
console.log('Metodo1 en Clase');
}
}
function Metodo2(parametro2: any, @DecoradorDeParametro parametroDecorado: any) {
//Error Decoradores no son validos aqui.
console.log('Funcion Global Metodo2');
}
const UnObjeto: TargetDemoClass = new TargetDemoClass();
UnObjeto.Metodo1('Clase Arg1', 'Clase Arg2 Decorado');
Metodo2('Global Arg1', 'Global Arg2');
Esto me marcara la función global con el siguiente error:
Implementado el primero cuando se ejecuta el metodo se hara un log de consola de los campos enviados. El Target es el objeto, el Key es el método, y el index es el indice del parámetro dentro de todos los parámetros para el ejemplo: el índice de parametro1 es 0, y el índice de parametroDecorado es 1. Obtendremos como resultado en el log.
Con esto finalizamos lo que respecta a los decoradores; continuaremos con su aplicación a Angular y otros lenguajes, para ver cómo todo esto de Typescript lo llevamos a un proyecto.
Posted on June 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.