ข้ามไปยังเนื้อหา

Decorators

decorator คือ function ที่ wrap หรือสังเกต class หรือ member ของ class ให้คุณเพิ่ม behavior แบบ declarative ด้วย syntax @name มีสอง design ที่เข้ากันไม่ได้ และสำคัญว่าคุณหมายถึงอันไหน:

  • legacy decorator — design เก่าแบบ experimental ที่เปิดด้วย experimentalDecorators ใน tsconfig ใช้เยอะใน Angular และ NestJS รุ่นเก่า signature ต่างกัน และพึ่ง reflect-metadata
  • stage-3 decorator — design ECMAScript ที่เป็นมาตรฐาน รองรับ native ตั้งแต่ TypeScript 5.0 โดยไม่ต้องใช้ flag พิเศษ อันนี้คือตัวที่ควรเรียนสำหรับ code ใหม่

บทเรียนนี้ครอบคลุม decorator แบบ stage-3

method decorator รับ method ต้นฉบับกับ context object แล้ว return ตัวแทน (หรือไม่ return อะไร) นี่คือ decorator @logged ที่ wrap method เพื่อ log การเรียกของตัวเอง:

function logged(originalMethod: any, context: ClassMethodDecoratorContext) {
const name = String(context.name);
function replacement(this: any, ...args: any[]) {
console.log(`calling ${name}`);
return originalMethod.call(this, ...args);
}
return replacement;
}
class Service {
@logged
fetch(id: number) {
return `item ${id}`;
}
}
new Service().fetch(1); // logs "calling fetch", then returns "item 1"

context object คือหัวใจของ design ใหม่ โดยบอก decorator ว่ากำลัง decorate อะไร — context.kind ("method", "field", "getter", …), context.name, context.static, context.private และ hook addInitializer สำหรับงาน setup

ชนิด decoratorรับอะไรใช้ทั่วไป
Classตัว class, contextregister class, wrap constructor
Methodmethod, contextwrap/แทน behavior (logging, timing)
Accessorgetter/setter, contextดักการ read/write
Fieldundefined, contextreturn initializer เพื่อแปลง initial value

field decorator ต่างออกไปนิด — ไม่ได้รับ value มา wrap แต่ return initializer function ที่แปลง initial value ของ field เมื่อสร้าง instance ได้

จะทำให้ decorator ตั้งค่าได้ ให้เขียน function ที่ return decorator — เรียกว่า decorator factory การเรียก @name(args) จะรัน factory แล้วค่าที่ return ออกมาคือ decorator จริง

function logged(label: string) { // factory: takes config
return function (originalMethod: any, context: ClassMethodDecoratorContext) {
return function (this: any, ...args: any[]) {
console.log(`[${label}] ${String(context.name)}`);
return originalMethod.call(this, ...args);
};
};
}
class Api {
@logged("http")
get() { /* ... */ }
}
code TypeScript ใหม่ควรใช้ decorator design แบบไหน?
argument ตัวที่สองของ stage-3 decorator (context) ให้อะไร?
decorator factory คืออะไร?
field decorator ต่างจาก method decorator อย่างไร?