Python

🎨 TypeScript Decorators: Panduan Lengkap

Pelajari TypeScript Decorators — class decorators, method decorators, property decorators, parameter decorators, dan penggunaan praktisnya

1. Pengenalan Decorators

Decorators adalah fitur experimental di TypeScript yang memungkinkan Anda menambahkan behavior ke class dan anggotanya secara deklaratif. Decorator menggunakan sintaks @expression dan banyak digunakan di framework seperti Angular, NestJS, dan TypeORM.

Decorators pada dasarnya adalah higher-order functions yang menerima target (class, method, property, atau parameter) dan bisa memodifikasi atau menambah behavior pada target tersebut.

Aktivasi Decorators

JSON — tsconfig.json
{
  "compilerOptions": {
    "target": "ES2021",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strict": true,
    "outDir": "./dist"
  }
}

// Catatan:
// experimentalDecorators — aktifkan decorator syntax
// emitDecoratorMetadata — aktifkan reflect metadata (perlu reflect-metadata package)

Decorator Execution Order

Diagram: Urutan Eksekusi Decorator
┌──────────────────────────────────────────────────────────┐
│          DECORATOR EXECUTION ORDER                       │
│                                                          │
│  1. Property Decorators    (kiri ke kanan)               │
│  2. Parameter Decorators   (kiri ke kanan)               │
│  3. Method Decorators      (kiri ke kanan)               │
│  4. Class Decorators       (bawah ke atas / kanan kiri)  │
│                                                          │
│  @ClassDecorator                                         │
│  class MyClass {                                         │
│    @PropertyDecorator                                    │
│    myProp: string;                                       │
│                                                          │
│    @MethodDecorator                                      │
│    myMethod(@ParameterDecorator param: string) {}        │
│  }                                                       │
│                                                          │
│  Eksekusi: Property → Parameter → Method → Class        │
└──────────────────────────────────────────────────────────┘

2. Class Decorators

Class decorator diterapkan pada constructor class. Class decorator bisa mengamati, memodifikasi, atau mengganti class constructor.

Class Decorator Dasar

TypeScript — Class Decorator
// Class decorator — menerima constructor sebagai argumen
function Logged<T extends { new (...args: any[]): {} }>(constructor: T) {
  console.log(`Class ${constructor.name} diinstansiasi`);
  return class extends constructor {
    createdAt = new Date();
  };
}

@Logged
class User {
  constructor(public nama: string, public email: string) {}
}

const user = new User("Budi", "budi@mail.com");
console.log(user);
// Class User diinstansiasi
// User { nama: "Budi", email: "budi@mail.com", createdAt: Date }

// Class decorator yang mengembalikan class baru
function Sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@Sealed
class Config {
  apiUrl = "https://api.example.com";
  timeout = 5000;
}

// ❌ Error: Cannot add property, object is not extensible
// (Config.prototype.newMethod = () => {}) — tidak bisa!

// Class decorator untuk singleton pattern
function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
  let instance: InstanceType<T>;

  return class extends constructor {
    constructor(...args: any[]) {
      if (instance) {
        return instance;
      }
      super(...args);
      instance = this as InstanceType<T>;
    }
  } as T;
}

@Singleton
class Database {
  constructor(public connectionString: string) {
    console.log("Database connection created");
  }
}

const db1 = new Database("mongodb://localhost:27017");
const db2 = new Database("mongodb://localhost:27017");
// "Database connection created" — hanya sekali!

console.log(db1 === db2);  // true — instance yang sama!

3. Method Decorators

Method decorator diterapkan pada method dari class. Method decorator bisa mengamati, memodifikasi, atau mengganti method descriptor.

Method Decorator Dasar

TypeScript — Method Decorator
// Method decorator — menerima 3 argumen:
// 1. target (prototype atau constructor)
// 2. propertyKey (nama method)
// 3. descriptor (PropertyDescriptor)
function Log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`📋 Memanggil ${propertyKey}(${JSON.stringify(args)})`);
    const result = originalMethod.apply(this, args);
    console.log(`✅ ${propertyKey} mengembalikan: ${JSON.stringify(result)}`);
    return result;
  };
}

class Kalkulator {
  @Log
  tambah(a: number, b: number): number {
    return a + b;
  }

  @Log
  kali(a: number, b: number): number {
    return a * b;
  }
}

const calc = new Kalkulator();
calc.tambah(5, 3);
// 📋 Memanggil tambah([5,3])
// ✅ tambah mengembalikan: 8

calc.kali(4, 7);
// 📋 Memanggil kali([4,7])
// ✅ kali mengembalikan: 28

Timing Decorator & Memoize

TypeScript — Timing & Memoize
// Timing decorator — mengukur waktu eksekusi method
function Timing(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`⏱️ ${propertyKey} selesai dalam ${(end - start).toFixed(2)}ms`);
    return result;
  };
}

// Memoize decorator — cache hasil method
function Memoize(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  const cache = new Map<string, any>();

  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log(`📦 Cache hit untuk ${propertyKey}`);
      return cache.get(key);
    }
    const result = originalMethod.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

class MathService {
  @Timing
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }

  @Memoize
  factorial(n: number): number {
    if (n <= 1) return 1;
    return n * this.factorial(n - 1);
  }
}

const math = new MathService();
console.log(math.factorial(10));  // 3628800
console.log(math.factorial(10));  // 📦 Cache hit — instant!
console.log(math.fibonacci(30));
// ⏱️ fibonacci selesai dalam 12.34ms

Retry Decorator

TypeScript — Retry Decorator
// Retry decorator — coba ulang jika gagal
function Retry(maxAttempts: number = 3, delay: number = 1000) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      let lastError: Error | undefined;

      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
          return await originalMethod.apply(this, args);
        } catch (error) {
          lastError = error as Error;
          console.log(`⚠️ ${propertyKey} gagal (attempt ${attempt}/${maxAttempts})`);
          if (attempt < maxAttempts) {
            await new Promise(r => setTimeout(r, delay));
          }
        }
      }
      throw lastError;
    };
  };
}

class ApiService {
  private callCount = 0;

  @Retry(3, 500)
  async fetchData(url: string): Promise<string> {
    this.callCount++;
    if (this.callCount < 3) {
      throw new Error("Network error");
    }
    return `Data dari ${url}`;
  }
}

// Akan otomatis retry 3 kali jika gagal
const api = new ApiService();
// api.fetchData("https://api.example.com").then(console.log);

4. Property Decorators

Property decorator diterapkan pada property (bukan value) dari class. Property decorator menerima 2 argumen: target dan propertyKey.

TypeScript — Property Decorator
// Property decorator — menerima 2 argumen:
// 1. target (prototype untuk instance property, constructor untuk static)
// 2. propertyKey (nama property)
function ReadOnly(target: any, propertyKey: string) {
  let value: any;

  // Override property descriptor
  Object.defineProperty(target, propertyKey, {
    get() {
      return value;
    },
    set(newValue: any) {
      if (value !== undefined) {
        console.warn(`⚠️ ${propertyKey} adalah readonly dan tidak bisa diubah!`);
        return;
      }
      value = newValue;
    },
    enumerable: true,
    configurable: true
  });
}

class AppConfig {
  @ReadOnly
  appName: string = "MyApp";

  @ReadOnly
  version: string = "1.0.0";

  timeout: number = 5000;
}

const config = new AppConfig();
console.log(config.appName);  // "MyApp"

config.appName = "NewApp";    // ⚠️ Warning: readonly!
console.log(config.appName);  // "MyApp" — tidak berubah!

config.timeout = 10000;       // ✅ Bisa diubah
console.log(config.timeout);  // 10000

// Required decorator — pastikan property diisi
function Required(target: any, propertyKey: string) {
  // Simpan metadata
  const requiredProps = Reflect.getMetadata("required", target) || [];
  requiredProps.push(propertyKey);
  Reflect.defineMetadata("required", requiredProps, target);
}

// Validation function
function validate(obj: any): string[] {
  const errors: string[] = [];
  const requiredProps = Reflect.getMetadata("required", obj) || [];

  for (const prop of requiredProps) {
    if (obj[prop] === undefined || obj[prop] === null || obj[prop] === "") {
      errors.push(`Property '${prop}' wajib diisi`);
    }
  }
  return errors;
}

class CreateUserDto {
  @Required
  nama: string = "";

  @Required
  email: string = "";

  bio?: string;
}

const dto = new CreateUserDto();
const errors = validate(dto);
console.log(errors);
// ["Property 'nama' wajib diisi", "Property 'email' wajib diisi"]

dto.nama = "Budi";
dto.email = "budi@mail.com";
console.log(validate(dto));  // [] — tidak ada error!

5. Parameter Decorators

Parameter decorator diterapkan pada parameter method. Parameter decorator menerima 3 argumen: target, propertyKey, dan parameterIndex.

TypeScript — Parameter Decorator
// Parameter decorator — menerima 3 argumen:
// 1. target (prototype)
// 2. propertyKey (nama method)
// 3. parameterIndex (posisi parameter, dimulai dari 0)
function RequiredParam(target: any, propertyKey: string, parameterIndex: number) {
  // Simpan metadata parameter yang required
  const requiredParams: number[] =
    Reflect.getOwnMetadata("requiredParams", target, propertyKey) || [];
  requiredParams.push(parameterIndex);
  Reflect.defineMetadata("requiredParams", requiredParams, target, propertyKey);
}

// Method decorator yang memvalidasi parameter
function Validate(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const requiredParams: number[] =
      Reflect.getOwnMetadata("requiredParams", target, propertyKey) || [];

    for (const index of requiredParams) {
      if (args[index] === undefined || args[index] === null) {
        throw new Error(
          `Parameter ke-${index} dari method '${propertyKey}' tidak boleh null/undefined`
        );
      }
    }
    return originalMethod.apply(this, args);
  };
}

class UserService {
  @Validate
  createUser(
    @RequiredParam nama: string,
    email: string,
    @RequiredParam umur: number
  ): object {
    return { nama, email, umur };
  }
}

const service = new UserService();

// ✅ Valid
console.log(service.createUser("Budi", "budi@mail.com", 25));

// ❌ Error: Parameter ke-0 dari method 'createUser' tidak boleh null/undefined
// service.createUser(null as any, "budi@mail.com", 25);

Inject Decorator

TypeScript — Inject Decorator
// Metadata keys
const INJECT_METADATA_KEY = Symbol("inject");

// Inject decorator — tandai parameter untuk dependency injection
function Inject(token: string) {
  return function (target: any, propertyKey: string | undefined, parameterIndex: number) {
    const existingParams: { index: number; token: string }[] =
      Reflect.getOwnMetadata(INJECT_METADATA_KEY, target) || [];
    existingParams.push({ index: parameterIndex, token });
    Reflect.defineMetadata(INJECT_METADATA_KEY, existingParams, target);
  };
}

// Simple DI Container
class Container {
  private services = new Map<string, any>();

  register<T>(token: string, instance: T): void {
    this.services.set(token, instance);
  }

  resolve<T>(constructor: new (...args: any[]) => T): T {
    const params: { index: number; token: string }[] =
      Reflect.getOwnMetadata(INJECT_METADATA_KEY, constructor) || [];

    const args: any[] = [];
    for (const { index, token } of params) {
      const service = this.services.get(token);
      if (!service) {
        throw new Error(`Service '${token}' tidak terdaftar`);
      }
      args[index] = service;
    }

    return new constructor(...args);
  }
}

// Contoh penggunaan
class UserController {
  constructor(
    @Inject("Logger") private logger: any,
    @Inject("Database") private db: any
  ) {}

  getUser(id: number): string {
    this.logger.log(`Fetching user ${id}`);
    return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

const container = new Container();
container.register("Logger", { log: (msg: string) => console.log(`[LOG] ${msg}`) });
container.register("Database", { query: (sql: string) => `Result: ${sql}` });

// Resolve otomatis inject dependencies
const controller = container.resolve(UserController);
controller.getUser(1);
// [LOG] Fetching user 1

6. Decorator Factories

Decorator factory adalah fungsi yang mengembalikan decorator. Ini memungkinkan Anda membuat decorator yang configurable dengan parameter.

TypeScript — Decorator Factories
// Decorator factory — fungsi yang mengembalikan decorator
function Throttle(delayMs: number) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    let lastCall = 0;

    descriptor.value = function (...args: any[]) {
      const now = Date.now();
      if (now - lastCall < delayMs) {
        console.log(`🚫 ${propertyKey} di-throttle, tunggu ${delayMs}ms`);
        return;
      }
      lastCall = now;
      return originalMethod.apply(this, args);
    };
  };
}

// Debounce decorator factory
function Debounce(delayMs: number) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    let timeoutId: ReturnType<typeof setTimeout>;

    descriptor.value = function (...args: any[]) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        originalMethod.apply(this, args);
      }, delayMs);
    };
  };
}

class SearchService {
  @Debounce(300)
  search(query: string): void {
    console.log(`🔍 Mencari: "${query}"`);
    // API call di sini
  }
}

const search = new SearchService();
search.search("a");    // di-cancel
search.search("ab");   // di-cancel
search.search("abc");  // ✅ Ini yang dieksekusi setelah 300ms

// Access control decorator factory
function Authorize(...roles: string[]) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      const currentUser = (this as any).currentUser;
      if (!currentUser || !roles.includes(currentUser.role)) {
        throw new Error(
          `🚫 Akses ditolak! Method '${propertyKey}' hanya untuk: ${roles.join(", ")}`
        );
      }
      return originalMethod.apply(this, args);
    };
  };
}

class AdminPanel {
  currentUser = { nama: "Budi", role: "admin" };

  @Authorize("admin")
  deleteUser(id: number): void {
    console.log(`User ${id} dihapus`);
  }

  @Authorize("admin", "moderator")
  banUser(id: number): void {
    console.log(`User ${id} di-ban`);
  }

  @Authorize("user", "admin", "moderator")
  viewProfile(id: number): void {
    console.log(`Viewing profile ${id}`);
  }
}

const panel = new AdminPanel();
panel.deleteUser(1);   // ✅ User 1 dihapus
panel.banUser(2);      // ✅ User 2 di-ban

7. Decorator Composition

Anda bisa menerapkan beberapa decorator pada target yang sama. Decorator dieksekusi secara tertentu tergantung posisinya.

TypeScript — Decorator Composition
// Beberapa decorator pada satu method
// Dieksekusi dari bawah ke atas (bottom-up)

function First(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log("First decorator evaluated");
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log("First: sebelum method");
    const result = original.apply(this, args);
    console.log("First: sesudah method");
    return result;
  };
}

function Second(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log("Second decorator evaluated");
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log("Second: sebelum method");
    const result = original.apply(this, args);
    console.log("Second: sesudah method");
    return result;
  };
}

class Example {
  @First     // dieksekusi kedua (outer)
  @Second    // dieksekusi pertama (inner)
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

const ex = new Example();
ex.greet("Budi");

// Output:
// Second decorator evaluated  ← evaluasi duluan
// First decorator evaluated   ← evaluasi belakangan
// First: sebelum method       ← eksekusi duluan (outer)
// Second: sebelum method      ← eksekusi kedua (inner)
// Second: sesudah method      ← kembali dari inner
// First: sesudah method       ← kembali dari outer

Composable Decorator Pattern

TypeScript — Composable Pattern
// Compose decorator — gabungkan beberapa decorator menjadi satu
function Compose(
  ...decorators: ((target: any, propertyKey: string, descriptor: PropertyDescriptor) => void)[]
) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // Reverse karena dieksekusi dari bawah ke atas
    for (const decorator of decorators.reverse()) {
      decorator(target, propertyKey, descriptor);
    }
  };
}

// Gabungkan beberapa behavior
const CachedAndLogged = Compose(
  function (target, key, desc) {
    console.log(`Setting up cache for ${key}`);
  },
  function (target, key, desc) {
    console.log(`Setting up logging for ${key}`);
  }
);

class DataProcessor {
  @CachedAndLogged
  process(data: string[]): string[] {
    return data.map(d => d.toUpperCase());
  }
}

// Practical: Create a comprehensive method decorator
function SafeExecute(options: {
  log?: boolean;
  retries?: number;
  fallback?: any;
} = {}) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.value;
    const { log = false, retries = 0, fallback = undefined } = options;

    descriptor.value = function (...args: any[]) {
      let lastError: Error | undefined;

      for (let i = 0; i <= retries; i++) {
        try {
          if (log) console.log(`[${propertyKey}] Attempt ${i + 1}`);
          return original.apply(this, args);
        } catch (err) {
          lastError = err as Error;
          if (log) console.log(`[${propertyKey}] Error: ${lastError.message}`);
        }
      }

      if (fallback !== undefined) return fallback;
      throw lastError;
    };
  };
}

class ExternalService {
  @SafeExecute({ log: true, retries: 2, fallback: "default data" })
  getData(): string {
    if (Math.random() < 0.7) throw new Error("Connection failed");
    return "real data";
  }
}

8. Praktik: Validation System

Mari kita gabungkan semua jenis decorator untuk membuat sistem validasi yang lengkap dan reusable, mirip seperti yang digunakan di framework seperti class-validator.

TypeScript — Validation System
// Metadata storage
const VALIDATION_METADATA = Symbol("validation");

interface ValidationRule {
  property: string;
  type: string;
  value?: any;
  message?: string;
}

// Helper untuk menyimpan metadata validasi
function addValidation(target: any, property: string, rule: Omit<ValidationRule, "property">) {
  const rules: ValidationRule[] =
    Reflect.getOwnMetadata(VALIDATION_METADATA, target) || [];
  rules.push({ ...rule, property });
  Reflect.defineMetadata(VALIDATION_METADATA, rules, target);
}

// ===== Property Decorators =====

function MinLength(min: number) {
  return function (target: any, propertyKey: string) {
    addValidation(target, propertyKey, {
      type: "minLength",
      value: min,
      message: `${propertyKey} minimal ${min} karakter`
    });
  };
}

function MaxLength(max: number) {
  return function (target: any, propertyKey: string) {
    addValidation(target, propertyKey, {
      type: "maxLength",
      value: max,
      message: `${propertyKey} maksimal ${max} karakter`
    });
  };
}

function IsEmail(target: any, propertyKey: string) {
  addValidation(target, propertyKey, {
    type: "isEmail",
    message: `${propertyKey} harus berupa email valid`
  });
}

function IsPositive(target: any, propertyKey: string) {
  addValidation(target, propertyKey, {
    type: "isPositive",
    message: `${propertyKey} harus bilangan positif`
  });
}

function Range(min: number, max: number) {
  return function (target: any, propertyKey: string) {
    addValidation(target, propertyKey, {
      type: "range",
      value: { min, max },
      message: `${propertyKey} harus antara ${min} dan ${max}`
    });
  };
}

// ===== Validator Engine =====

function validate(instance: any): string[] {
  const errors: string[] = [];
  const rules: ValidationRule[] =
    Reflect.getOwnMetadata(VALIDATION_METADATA, Object.getPrototypeOf(instance)) || [];

  for (const rule of rules) {
    const value = (instance as any)[rule.property];

    switch (rule.type) {
      case "minLength":
        if (typeof value === "string" && value.length < rule.value) {
          errors.push(rule.message!);
        }
        break;
      case "maxLength":
        if (typeof value === "string" && value.length > rule.value) {
          errors.push(rule.message!);
        }
        break;
      case "isEmail":
        if (typeof value === "string" && !value.includes("@")) {
          errors.push(rule.message!);
        }
        break;
      case "isPositive":
        if (typeof value === "number" && value <= 0) {
          errors.push(rule.message!);
        }
        break;
      case "range":
        if (typeof value === "number" && (value < rule.value.min || value > rule.value.max)) {
          errors.push(rule.message!);
        }
        break;
    }
  }

  return errors;
}

// ===== Validatable class decorator =====
function Validatable<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    validate(): string[] {
      return validate(this);
    }

    isValid(): boolean {
      return this.validate().length === 0;
    }
  };
}

// ===== Menggunakan Validation System =====

@Validatable
class RegistrationForm {
  @MinLength(3)
  @MaxLength(50)
  nama: string;

  @IsEmail
  email: string;

  @MinLength(8)
  password: string;

  @Range(17, 100)
  umur: number;

  constructor(nama: string, email: string, password: string, umur: number) {
    this.nama = nama;
    this.email = email;
    this.password = password;
    this.umur = umur;
  }
}

// Test
const form1 = new RegistrationForm("Budi", "budi@mail.com", "password123", 25);
console.log((form1 as any).validate());  // [] ✅ Tidak ada error

const form2 = new RegistrationForm("A", "bukan-email", "123", 10);
console.log((form2 as any).validate());
// [
//   "nama minimal 3 karakter",
//   "email harus berupa email valid",
//   "password minimal 8 karakter",
//   "umur harus antara 17 dan 100"
// ]

console.log((form1 as any).isValid());  // true
console.log((form2 as any).isValid());  // false
⚠️ Catatan Penting

TypeScript Decorators masih dalam tahap experimental (TC39 Stage 3). Syntax dan API bisa berubah di versi mendatang. Untuk production, gunakan library yang sudah stabil seperti class-validator, TypeORM, atau NestJS decorators. Pastikan juga menginstal reflect-metadata untuk metadata reflection.

9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang TypeScript Decorators:

Pertanyaan 1: Apa fungsi dari experimentalDecorators di tsconfig.json?

a) Mengaktifkan fitur Generics
b) Mengaktifkan decorator syntax (@decorator)
c) Mengaktifkan strict mode
d) Mengaktifkan ES modules

Pertanyaan 2: Apa yang diterima oleh method decorator sebagai argumen?

a) Hanya constructor class
b) target, propertyKey, dan descriptor
c) Hanya nama method
d) target dan parameterIndex

Pertanyaan 3: Apa itu decorator factory?

a) Class khusus untuk membuat decorator
b) Fungsi yang mengembalikan decorator, memungkinkan parameterisasi
c) Interface untuk decorator
d) Library untuk membuat decorator

Pertanyaan 4: Dalam urutan apa @First @Second dieksekusi pada method?

a) @First duluan, @Second kedua
b) @Second duluan, @First kedua (bottom-up evaluation)
c) Keduanya bersamaan
d) Bergantung pada urutan di file

Pertanyaan 5: Apa yang dilakukan emitDecoratorMetadata di tsconfig.json?

a) Menyembunyikan metadata decorator
b) Menghasilkan type metadata otomatis untuk decorator (perlu reflect-metadata)
c) Mengkompilasi decorator ke JavaScript
d) Menghapus decorator saat compile
🔍 Zoom
100%
🎨 Tema