Decorators — TypeScript

Decorator adalah fungsi yang "menempel" pada class, method, atau property untuk menambah perilaku tanpa mengubah kode aslinya. Kamu mungkin sudah lihat di…

Decorator adalah fungsi yang kamu tempelkan ke class atau method dengan tanda @, untuk menambah perilaku tanpa mengubah isi method-nya. Mirip bungkus kado: barang di dalamnya tetap sama, tapi bungkusnya menambah sesuatu, misalnya kartu ucapan yang ikut terbaca tiap kali kadonya dibuka. Decorator @logged membungkus method total: hitungannya tetap harga kali jumlah, tapi sekarang tiap panggilan dicatat di console.

🧪 Tebakan cepat: kasir.total(...) dipanggil 3 kali. Berapa kali fungsi logged sendiri dijalankan: 1, 3, atau 4 kali? Tebak dulu, jawabannya ada di bagian "Kapan decorator jalan".

Method decorator: terima method, kembalikan penggantinya

Sejak TypeScript 5.0, decorator mengikuti standar JavaScript (proposal TC39 tahap 3) dan langsung jalan tanpa pengaturan apa pun. Method decorator adalah fungsi biasa dengan dua parameter:

function logged(method: (...args: number[]) => number, context: ClassMethodDecoratorContext) {
  const nama = String(context.name)
  return function (this: unknown, ...args: number[]): number {
    console.log(`-> ${nama}(${args.join(", ")})`)
    const hasil = method.apply(this, args)
    console.log(`<- ${nama} = ${hasil}`)
    return hasil
  }
}

class Kasir {
  @logged
  total(harga: number, jumlah: number): number {
    return harga * jumlah
  }
}

const kasir = new Kasir()
kasir.total(15000, 2)
-> total(15000, 2)
<- total = 30000

Kapan decorator jalan

Fungsi logged jalan sekali, waktu class Kasir dibuat, sebelum ada yang memanggil total. Yang jalan di tiap panggilan adalah fungsi pengganti yang dia kembalikan. Jadi kalau total dipanggil 3 kali, logged tetap cuma jalan 1 kali, dan penggantinya 3 kali. Itu jawaban tebakannya.

Kalau satu method punya dua decorator, keduanya ditulis bertumpuk:

class Kasir {
  @pertama
  @kedua
  total(harga: number, jumlah: number): number { ... }
}

Yang paling dekat dengan method dipasang duluan: kedua membungkus total, lalu pertama membungkus hasilnya.

Class decorator

Decorator juga bisa ditempel ke class. Dia menerima class-nya sendiri dan context dengan kind: "class":

const daftar: string[] = []

function terdaftar(value: Function, context: ClassDecoratorContext) {
  daftar.push(String(context.name))
}

@terdaftar
class Kasir { ... }

console.log(daftar) // ["Kasir"]

Begitu class Kasir dibuat, namanya masuk ke daftar. Pola ini yang dipakai framework untuk mendaftarkan class, misalnya @Component di Angular atau @Controller di NestJS.

Dua versi: standar dan experimentalDecorators

Sebelum TypeScript 5.0, decorator cuma ada versi lama yang dinyalakan dengan "experimentalDecorators": true di tsconfig. Versi lama itu berbeda dari standar, dan framework yang lebih dulu memakai decorator, seperti Angular, NestJS dan TypeORM, dibangun di atasnya. Itu sebabnya project NestJS dan TypeORM masih menuntut flag itu.

Standar (TypeScript 5.0+) experimentalDecorators: true
Method decorator menerima (method, context) (target, propertyKey, descriptor)
Yang di-return fungsi pengganti descriptor (descriptor.value diganti)
Decorator di parameter constructor gak ada ada, misalnya @Inject()
emitDecoratorMetadata gak bisa bisa

Versi lama dari logged yang sama:

// tsconfig: "experimentalDecorators": true
function logged(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
  const asli = descriptor.value
  descriptor.value = function (this: unknown, ...args: unknown[]) {
    console.log(`-> ${propertyKey}(${args.join(", ")})`)
    const hasil = asli.apply(this, args)
    console.log(`<- ${propertyKey} = ${hasil}`)
    return hasil
  }
  return descriptor
}

Hasil di console-nya sama persis. Bedanya cuma apa yang diterima dan dikembalikan decorator-nya.

⚠️ Jebakan yang sering ketemu

TL;DR: Decorator adalah fungsi yang ditempel dengan @nama ke class atau method. Versi standar (TypeScript 5.0+, tanpa flag) memberi method decorator dua hal, method aslinya dan context (kind, name, ...), dan yang di-return menggantikan method itu. Fungsi decorator jalan sekali waktu class dibuat; penggantinya yang jalan di tiap panggilan. Versi lama butuh "experimentalDecorators": true, menerima (target, propertyKey, descriptor), dan masih dituntut NestJS dan TypeORM karena formatnya berbeda dari standar. Jangan campur keduanya.

Yang akan kamu pelajari