Modulos y Decoradores en TypeScript
Módulos en TypeScript[editar]
¿Qué son los módulos en TypeScript?[editar]
Los módulos en TypeScript son archivos que contienen código que se puede reutilizar en otras partes de una aplicación. Los módulos ayudan a organizar el código y a mantenerlo separado en unidades lógicas. Cada módulo tiene su propio ámbito, lo que significa que las variables, funciones y clases definidas en un módulo no están disponibles fuera de él a menos que se exporten explícitamente.
Exportar desde el módulo[editar]
Para hacer disponible algo desde un módulo, se debe exportar. Hay dos tipos principales de exportaciones:
- Exportaciones nombradas: Puedes exportar múltiples cosas desde un módulo. Cuando las importes en otro módulo, debes usar el mismo nombre con el que se ha exportado.
// math.ts
export function add(x: number, y: number): number {
return x + y;
}
export function subtract(x: number, y: number): number {
return x - y;
}
- Exportaciones por defecto: Cada módulo puede tener una exportación por defecto. Cuando la importes desde otro módulo, puedes darle otro nombre si quieres.
// multiply.ts
export default function multiply(x: number, y: number): number {
return x * y;
}
Importaciones[editar]
Para usar el código exportado en otro archivo, se debe importar.
- Importaciones de exportaciones nombradas: Si nuestro módulo es el fichero math.ts, importamos lo que necesitemos de las exportaciones nombradas, dando el mismo nombre con el que se exportaron.
// main.ts
import { add, subtract } from './math';
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
- Importaciones de exportaciones por defecto: Si nuestro módulo es multiply.ts, importamos la exportación por defecto dándole el nombre que queramos, myMultiply en el siguiente código.
// main.ts
import myMultiply from './multiply';
console.log(myMultiply(5, 3)); // 15
Decoradores en TypeScript[editar]
¿Qué son los decoradores?[editar]
Si tenemos una clase con propiedades, métodos y parámetros en la llamada a los método, podemos cambiar el comportamiento o añadir funcionalidad adicional al constructor de la clase, propiedades, métodos o parámetros. Para ello, definimos una función TypeScript con dicha modificación de comportamiento, digamos que llamamos a esa función myDecorator(). Una vez definida, podemos añadir dicha función, como decorador, a la clase, propiedad, método o parámetro. Basta con ponerle delante @myDecorator.
Es importante indicar que a la función decorador se la llamará una única vez cuando se defina la clase decorada, el método, el atributo o el parámetro. No se llamará a la función decorador cada vez que hagamos un new de la clase, o usemos el atributo, o llamemos al método. Si queremos hacer algo cada vez que se llame a uno de estos elementos, debemos reemplazar o modificar el elemento original (la clase, método o atributo) para que haga algo distinto cada vez que se le use.
- Los decoradores de clase pueden devuelver clases hijas de la original con alguna modificación.
- Los decoradores de método devuelven un
PropertyDescriptor
con la función original modificada. - Los decoradores de atributo añaden un
PropertyDescriptor
con las modificaciones al atributo original - Los decoradores de parámetro no pueden hacer ninguna modificación en el parámetro, solo recoger que han sido anotados en la definición.
Tipos de Decoradores[editar]
Como hemos comentado, los decoradores se pueden aplicar a varios elementos del código. Esto define los tipos de decoradores que hay según a quién apliquen: de clase, de método, de atributo y de parámetro.
Veamos cada uno de ellos con un ejemplo. No entraremos mucho en detalles, lo importante es quedarse con el concepto. Algunas de las sintaxis para los decoradores son experimentales de TypeScript, por lo que para evitar problemas, es mejor asegurarse de habilitar,en el fichero tsconfig.json, la opción "experimentalDecorators": true dentro de "compilerOptions"
{
"compilerOptions": {
...
"experimentalDecorators": true,
...
}
Si no lo pones, también hay posibilidad de usar decoradores, pero la sintaxis es más estricta.
Decoradores de clase[editar]
Se aplica a una clase. Para el ejemplo vamos a hacer un decorador (función classDecorator
) que saque por console
el nombre de la clase que se está instanciando. Para añadir esto a una clase, nos bastará añadirle el decorador @classDecorator
. El siguiente puede ser el ejemplo de código.
// Función que hace de decorador de clase.
// Devuelve una clase hija de la clase original con la funcionalidad añadida.
function classDecorator<T extends { new (...args: any[]): {} }>(originalClass: T) {
return class extends originalClass {
constructor(...args: any[]){
super(args);
console.log(originalClass); // Sacará por pantalla [class MyClass]
}
}
}
// Clase con el decorador
@classDecorator
export class MyClass {
param: string;
constructor(parameter: string) {
console.log('My class: '+parameter);
this.param = parameter;
}
}
La función decorador classDecorator
recibe como parámetro una clase originalClass
y devuelve una clase hija de la misma con algo de funcionalidad añadida. Esta funcionalidad añadida será que saque un mensaje por console
cada vez que se instancia la clase.
Si quieres ver una explación detallada del código, puedes ver Decorador de Clase en TypeScript
Decoradores de método[editar]
Se aplican a los métodos. Como antes, haremos que se saque un log con console
cada vez que se llame a un método.
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Método ${propertyKey} llamado con argumentos: ${args}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
export class MyClass {
@methodDecorator
myFunction(params:string): void {
console.log("myFunction: " + params);
}
}
La función decoradora recibe varios parámetros. En descriptor.value
está la función original que hemos decorado. Nos basta con reemplazar este valor descriptor.value
por la función que queramos y delvolver el nuevo descriptor
.
Para que este decorador se pueda usar en cualquier método, hacemos que la función que escribmos para reemplazar admita cualquier número de parámetros de cualquier tipo ...args: any[]
. Esta función, después de escribir el log en console
, llama a la original con originalMethod.apply()
.
Decoradores de propiedad[editar]
Se aplican a las propiedades de las clases. Como ejemplo aseguraremos con un decorador que no se asigna una cadena vacía a una propiedad de una clase. El código puede ser el siguiente
function NotEmpty(target: any, propertyKey: string) {
let value:string;
const descriptor: PropertyDescriptor = {
get() {
return value;
},
set(newValue: string) {
if (newValue == "") {
throw new Error(`El valor "${newValue}" puede estar vacía.`);
}
value = newValue;
}
};
Object.defineProperty(target, propertyKey, descriptor);
}
export class MyClass {
@NotEmpty
param: string;
constructor(parameter: string) {
console.log('My class: '+parameter);
this.param = parameter;
}
}
La función que define el decorador recibe el parámetro y su key (target
y propertyKey
). Crea para ellos un PropertyDescriptor
con los métodos get()
y set()
indicando la funcionalidad de esos métodos. Luego, con Oject.defineProperty()
asigna este PropertyDescriptor al parámetro pasando como identificadores target, propertyKey
y como valor el descriptor
.
Finalmente, basta decorar en la clase la propiedad que queramos con @NotEmpty
.
Decoradores de parámetro[editar]
Se aplican a los parámetros de los métodos.
function required(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Parameter at position ${parameterIndex} in method ${propertyKey} is required.`);
}
class Greeter {
greet(@required name: string) {
return `Hello, ${name}`;
}
}
En este caso no podemos hacer ninguna modificación, sólo enterarnos de que un parámetro se marcado con este decorador. Para darle utilidad, deberíamos guardar los parámetros decorados que se reciban en el método required()
en algún sitio y luego combinarlo con un decorador de método para ver, en ese decorador, si los parámetros required
cumplen nuestras condiciones.