Decoradores
## Objetivos de Aprendizaje
- Comprender el concepto de decorador
- Usar decoradores de clase
- Aplicar decoradores de metodo
- Trabajar con decoradores de propiedad
- Configurar TypeScript para decoradores
## Descripcion General
Los decoradores son una caracteristica experimental (estadio 2) que permiten modificar clases y sus miembros. Proporcionan una forma de agregar metadatos o cambiar comportamiento.
## Habilitar Decoradores
### tsconfig.json
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
## Decoradores de Clase
### Sintaxis Basica
```typescript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
```
### Fabricas de Decoradores
```typescript
function color(value: string) {
return function (constructor: Function) {
constructor.prototype.color = value;
};
}
@color("blue")
class Car {
brand: string;
}
console.log((new Car() as any).color); // "blue"
```
## Decoradores de Metodo
### Decoradores de Accesor
```typescript
function readonly(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.writable = false;
}
class Person {
@readonly
name(): string {
return "Alice";
}
}
let p = new Person();
// p.name = () => "Bob"; // Error: No se puede asignar
```
### Decorador de Metodo
```typescript
function log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Llamando ${propertyKey} con`, args);
return original.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
let calc = new Calculator();
calc.add(2, 3);
// Llamando add con [2, 3]
// 5
```
## Decoradores de Propiedad
```typescript
function defaultValue(value: any) {
return function (
target: any,
propertyKey: string
) {
target[propertyKey] = value;
};
}
class Config {
@defaultValue("localhost")
host: string;
}
let config = new Config();
console.log(config.host); // "localhost"
```
## Decoradores de Parametro
```typescript
function required(
target: any,
propertyKey: string | symbol,
parameterIndex: number
) {
// Marcar parametro como requerido
}
function validate(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
// Validar parametros requeridos
return original.apply(this, args);
};
}
class UserService {
create(
@required name: string,
@required age: number
): User {
return { name, age };
}
}
```
## Composicion de Decoradores
### Multiples Decoradores
```typescript
function first() {
console.log("first(): evaluado");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("first(): llamado");
};
}
function second() {
console.log("second(): evaluado");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("second(): llamado");
};
}
class Example {
@first()
@second()
method() {}
}
// Salida:
// first(): evaluado
// second(): evaluado
// second(): llamado
// first(): llamado
```
## Decoradores de Metodos de Clase
### Decorador de Registro
```typescript
function logExecution(label: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = Date.now();
const result = original.apply(this, args);
const duration = Date.now() - start;
console.log(`${label} tomo ${duration}ms`);
return result;
};
};
}
class UserService {
@logExecution("getUser")
getUser(id: number) {
// operacion costosa
return { id, name: "Alice" };
}
}
```
## Descriptor de Propiedad
### Propiedades Disponibles
```typescript
{
value?: any,
writable?: boolean,
enumerable?: boolean,
configurable?: boolean,
get?: () => any,
set?: (v: any) => void
}
```
## Reflect Metadata
### Instalacion
```bash
npm install reflect-metadata
```
### Ejemplo
```typescript
import "reflect-metadata";
function required(
target: Object,
propertyKey: string,
parameterIndex: number
) {
const requiredParams: number[] =
Reflect.getMetadata("required", target, propertyKey) || [];
requiredParams.push(parameterIndex);
Reflect.defineMetadata(
"required",
requiredParams,
target,
propertyKey
);
}
class Person {
greet(@required name: string): string {
return `Hola, ${name}`;
}
}
```
## Casos de Uso Comunes
### Patron Singleton
```typescript
function singleton(
constructor: T
) {
let instance: T;
return class extends constructor {
constructor(...args: any[]) {
if (instance) {
return instance;
}
instance = this as T;
super(...args);
}
};
}
@singleton
class Database {
constructor() {
console.log("Base de datos conectada");
}
}
let db1 = new Database();
let db2 = new Database();
console.log(db1 === db2); // true
```
### Auto-bind
```typescript
function autoBind(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
return {
configurable: true,
enumerable: false,
get() {
return original.bind(this);
}
};
}
class Component {
@autoBind
render() {
console.log(this);
}
}
```
## Limitaciones
### Experimental
- No es parte oficial de ECMAScript aun
- La API puede cambiar
- Usar con cuidado en produccion
### Orden de Ejecucion
- Decoradores evaluados de arriba a abajo
- Decoradores llamados de abajo a arriba
- Tener cuidado al combinar multiples decoradores
## Resumen
- Los decoradores son experimentales en TypeScript
- Habilitar con `experimentalDecorators`
- Los decoradores de clase reciben constructor
- Los decoradores de metodo/accesor reciben descriptor
- Los decoradores de propiedad reciben prototipo
- Los decoradores de parametro reciben indice
- Las fabricas de decoradores retornan funciones de decorador
- Multiples decoradores se componen con orden de evaluacion/llamada
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →