Decorators

Pengenalan

Dengan pengenalan Kelas-kelas yang ada di TypeScript dan ES6, sekarang ada skenario tertentu yang memerlukan fitur tambahan untuk mendukung anotasi atau modifikasi kelas dan anggota kelas. Decorators menyediakan cara untuk menambahkan anotasi-anotasi dan sebuah sintaks pemrogragaman meta untuk deklarasi kelas dan anggota kelas. Decorators ada pada stage 2 proposal untuk JavaScript dan juga tersedia pada TypeScript sebagai fitur eksperimental.

CATATAN  Decorators adalah fitur eksperimental yang mungkin dapat berubah ketika dirilis nanti.

Untuk mengaktifkan Decorators eksperimental, anda harus menambahkan opsi experimentalDecorators ke baris perintah atau ke berkas tsconfig.json.

Command Line:

shell
tsc --target ES5 --experimentalDecorators

tsconfig.json:

{
"": "ES5",
}
}

Decorator

Decorator adalah jenis deklarasi khusus yang dapat dilampirkan ke deklarasi kelas, method, accessor, property, atau parameter. Decorators menggunakan bentuk @expression, dimana expression harus mengevaluasi fungsi yang akan dipanggil saat proses dengan informasi tentang deklarasi yang didekorasi.

Sebagai contoh, ada decorator @sealed yang mungkin kita akan menuliskan fungsi sealed sebagai berikut:

ts
function sealed(target) {
// lakukan sesuatu dengan 'target' ...
}

CATATAN  Anda dapat melihat contoh lengkapnya di Decorator Kelas.

Decorator Factories

Jika kita ingin menyesuaikan penerapan decorator pada sebuah deklarasi, kita dapat menuliskan sebuah decorator factory. Decorator Factory adalah sebuah fungsi yang mengembalikan ekspresi yang akan dipanggil oleh decorator ketika proses.

Kita dapat menuliskan decorator factory seperti berikut:

ts
function color(value: string) {
// ini adalah decorator factory
return function (target) {
// ini adalah decorator
// lakukan sesuatu dengan 'target' dan 'value'...
};
}

CATATAN  Anda dapat melihat contoh lengkap dari penggunaan decorator factory di Method Decorators

Komposisi Decorator

Lebih dari satu decorator dapat diterapkan pada sebuah deklarasi, seperti contoh berikut:

  • Penerapan dengan satu baris:

    ts
    @f @g x
  • Penerapan lebih dari satu baris:

    ts
    @f
    @g
    x

Ketika lebih dari satu decorator diterapkan ke sebuah deklarasi, evaluasi yang dilakukan mirip seperti fungsi komposisi pada matematika. Pada model ini, ketika mengkomposisikan fungsi f dan g, maka akan menjadi (fg)(x) yang sama dengan f(g(x)).

Dengan demikian, langkah-langkah berikut dilakukan saat mengevaluasi beberapa decorator pada satu deklarasi di TypeScript:

  1. Ekspresi untuk setiap decorator dievaluasi dari atas ke bawah.
  2. Hasilnya kemudian disebut sebagai fungsi dari bawah ke atas.

JIka kita menggunakan decorator factories, kita dapat mengamati urutan evaluasi ini dengan contoh berikut:

ts
function f() {
console.log("f(): evaluated");
return function (
target,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("f(): called");
};
}
function g() {
console.log("g(): evaluated");
return function (
target,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("g(): called");
};
}
class C {
@f()
@g()
method() {}
}

Yang akan mencetak keluaran ini ke console:

shell
f(): evaluated
g(): evaluated
g(): called
f(): called

Evaluasi Decorator

Ada urutan yang jelas tentang bagaimana decorator diterapkan ke berbagai deklarasi yang ada di dalam kelas:

  1. Parameter Decorators, diikuti oleh Method, Accessor, atau Property Decorators diterapkan untuk setiap anggota instance.
  2. Parameter Decorators, diikuti oleh Method, Accessor, atau Property Decorators diterapkan untuk setiap anggota statis.
  3. Parameter Dekorator diterapkan untuk konstruktor.
  4. Class Decorators diterapkan untuk kelas.

Decorator Kelas

Class Decorator dideklarasikan tepat sebelum deklarasi kelas. Decorator kelas diterapkan ke konstruktor kelas dan dapat digunakan untuk mengamati, memodifikasi, atau mengganti definisi kelas. Decorator kelas tidak dapat digunakan dalam berkas deklarasi, atau dalam konteks ambien lainnya (seperti pada kelas deklarasi).

Ekspresi untuk decorator kelas akan dipanggil sebagai fungsi pada waktu proses, dengan konstruktor kelas yang didekorasi sebagai satu-satunya argumennya.

Jika decorator kelas mengembalikan nilai, deklarasi kelas akan diganti dengan fungsi konstruktor yang disediakan.

CATATAN  Jika Anda memilih untuk mengembalikan fungsi konstruktor baru, Anda harus berhati-hati dalam mempertahankan prototipe asli. Logika yang menerapkan dekorator pada waktu proses tidak akan melakukannya untukmu.

Berikut ini adalah contoh decorator kelas (@sealed) yang diterapkan ke kelas Greeter:

ts
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

Kita dapat mendefinisikan decorator @sealed menggunakan deklarasi fungsi berikut:

ts
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

Ketika @sealed dijalankan, itu akan menyegel konstruktor dan prototipenya.

Selanjutnya kita memiliki contoh bagaimana menimpa konstruktor.

ts
function classDecorator<T extends { new (...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
newProperty = "new property";
hello = "override";
};
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));

Method Decorators

Method Decorator dideklarasikan tepat sebelum deklarasi method. Dekorator diterapkan ke Property Descriptor untuk method, yang dapat digunakan untuk mengamati, memodifikasi, atau mengganti definisi method. Method Decorator tidak dapat digunakan dalam berkas deklarasi, saat kelebihan beban, atau dalam konteks ambien lainnya (seperti dalam kelas declare).

Ekspresi untuk method decorator akan dipanggil sebagai fungsi pada waktu proses, dengan tiga argumen berikut:

  1. Bisa memiliki fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
  2. Nama anggota.
  3. The Property Descriptor untuk anggota.

CATATAN  Property Descriptor akan menjadi undefined jika target skripmu dibawah ES5.

Jika method decorator mengembalikan sebuah nilai, maka akan digunakan sebagai Property Descriptor untuk method.

CATATAN  Nilai yang dikembalikan akan dibiarkan, jika target kodemu dibawah ES5.

Berikut adalah contoh penerapan method decorator (@enumerable) ke method yang ada pada kelas Greeter:

ts
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}

Kita dapat mendefinisikan decorator @enumerable menggunakan fungsi deklarasi berikut:

ts
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}

Decorator @enumerable(false) disini adalah sebuah decorator factory. Ketika decorator @enumerable(false) dipanggil, ia akan merubah enumerable properti dari properti descriptor.

Decorator Aksesor

Sebuah Accessor Decorator dideklarasikan tepat sebelum sebuah deklarasi aksesor. Decorator aksesor diterapkan ke Property Descriptor untuk aksesor dan dapat digunakan untuk mengamati, memodifikasi, atau mengganti definisi aksesor. Decorator aksesor tidak dapat digunakan dalam deklarasi berkas, atau dalam konteks ambien lainnya (seperti dalam kelas declare).

CATATAN  TypeScript melarang penerapan decorator ke aksesor get dan set untuk single member. Sebaliknya, semua decorator untuk anggota harus diterapkan ke pengakses pertama yang ditentukan dalam urutan dokumen. Ini karena decorator berlaku untuk Property Descriptor, yang menggabungkan aksesor get dan set, bukan setiap deklarasi secara terpisah.

Ekspresi untuk decorator pengakses akan dipanggil sebagai fungsi pada waktu proses, dengan tiga argumen berikut:

  1. Bisa memiliki fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
  2. Nama anggota.
  3. The Property Descriptor untuk anggota.

CATATAN  Property Descriptor akan menjadi undefined, jika target skripmu dibawah ES5.

Jika aksesor decorator mengembalikan sebuah nilai, ia akan digunakan sebagai Property Descriptor untuk anggota.

CATATAN  Nilai yang dikembalikan akan dibiarkan, jika target skripmu dibawah ES5.

Berikut ada contoh penerapan aksesor decorator (@configurable) ke anggota kelas Point:

ts
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}

Kita dapat mendefinisikan decorator @configurable menggunakan deklarasi fungsi berikut:

ts
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}

Property Decorators

Sebuah Property Decorator dideklarasikan tepat sebelum deklarasi properti. Property Decorator tidak dapat digunakan dalam deklarasi berkas, atau dalam konteks ambien lainnya (seperti dalam kelas declare).

Ekspresi untuk properti decorator akan dipanggil sebagai fungsi pada waktu proses, dengan dua argumen berikut:

  1. Dapat berupa fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
  2. Nama anggota.

CATATAN  Property Descriptior tidak menyediakan sebuah argumen untuk properti decorator karena bergantung tentang bagaimana properti decorator diinisialisasi pada TypeScript. Ini karena, saat ini tidak ada mekanisme untuk mendeskripsikan sebuah instance properti ketika mendefinisikan anggota dari sebuah prototipe, dan tidak ada cara untuk mengamati atau memodifikasi initializer untuk properti. Dan nilai kembalian juga akan dibiarkan. Sehingga, sebuah properti decorator hanya bisa digunakan untuk mengamati properti dengan nama yang spesifik, yang telah dideklarasikan pada sebuah kelas.

Kita dapat menggunakan informasi tersebut untuk memantau properti metadata, seperti pada contoh berikut:

ts
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}

Kemudian, kita dapat mendefinisikan decorator @format dan fungsi getFormat dengan menggunakan deklarasi fungsi berikut:

ts
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

Decorator @format("Hello, %s") disini adalah sebuah decorator factory. Ketika @format("Hello, %s") dipanggil, ia akan menambahkan properti metadata menggunakan fungsi Reflect.metadata dari pustaka reflect-metadata. Ketika getFormat dipanggil, ia akan membaca format dari nilai metadata-nya.

CATATAN  Contoh ini membutuhkan pustaka reflect-metadata. Lihat Metadata untuk informasi lebih lanjut mengenai pustaka reflect-metadata.

Parameter Decorators

Parameter Decorator dideklarasikan tepat sebelum a parameter dideklarasikan. Parameter decorator diterapkan ke fungsi konstruktor pada kelas atau saat deklarasi method. Parameter decorator tidak dapat digunakan dalam deklarasi berkas, overload, atau dalam konteks ambien lainnya (seperti dalam kelas declare).

Ekspresi untuk parameter decorator akan dipanggil sebagai fungsi pada waktu proses, dengan tiga argumen berikut:

  1. Dapat berupa fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
  2. Nama anggota.
  3. Indeks ordinal dari parameter dalam daftar parameter fungsi.

CATATAN  Sebuah parameter decorator hanya bisa digunakan untuk mengamati sebuah parameter yang telah dideklarasikan pada sebuah method.

Nilai kembalian dari parameter decorator akan dibiarkan.

Berikut adalah contoh penggunaan parameter decorator (@required) pada anggota kelas Greeter:

ts
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}

Kemudian, kita dapat mendefinisikan decorator @required dan @validate menggunakan deklarasi fungsi berikut:

ts
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) {
let existingRequiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(
requiredMetadataKey,
existingRequiredParameters,
target,
propertyKey
);
}
function validate(
target: any,
propertyName: string,
descriptor: TypedPropertyDescriptor<Function>
) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(
requiredMetadataKey,
target,
propertyName
);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (
parameterIndex >= arguments.length ||
arguments[parameterIndex] === undefined
) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}

Decorator @required menambahkan entri metadata yang menandakan bahwa parameter tersebut diperlukan. Decorator @validate kemudian akan memvalidasi semua argumen yang ada, sebelum method-nya dijalankan.

CATATAN  Contoh ini memerlukan pustaka reflect-metadata Lihat Metadata untuk informasi lebih lanjut mengenai pustaka reflect-metadata.

Metadata

Beberapa contoh menggunakan pustaka reflect-metadata yang menambahkan polyfill untuk API metadata eksperimental. Pustaka ini belum menjadi bagian dari standar ECMAScript (JavaScript). Namun, ketika decorator secara resmi diadopsi sebagai bagian dari standar ECMAScript, ekstensi ini akan diusulkan untuk diadopsi.

Anda dapat memasang pustaka ini melalui npm:

shell
npm i reflect-metadata --save

TypeScript menyertakan dukungan eksperimental untuk menghadirkan jenis metadata tertentu untuk deklarasi yang memiliki decorator. Untuk mengaktifkan dukungan eksperimental ini, Anda harus mengatur opsi kompilator emitDecoratorMetadata baik pada baris perintah atau di tsconfig.json Anda:

Command Line:

shell
tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

tsconfig.json:

Ketika diaktifkan, selama pustaka reflect-metadata di-import, informasi jenis design-time tambahan akan diekspos saat runtime.

Kita dapat melihat action pada contoh berikut:

ts
import "reflect-metadata";
class Point {
x: number;
y: number;
}
class Line {
private _p0: Point;
private _p1: Point;
@validate
set p0(value: Point) {
this._p0 = value;
}
get p0() {
return this._p0;
}
@validate
set p1(value: Point) {
this._p1 = value;
}
get p1() {
return this._p1;
}
}
function validate<T>(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>
) {
let set = descriptor.set;
descriptor.set = function (value: T) {
let type = Reflect.getMetadata("design:type", target, propertyKey);
if (!(value instanceof type)) {
throw new TypeError("Invalid type.");
}
set.call(target, value);
};
}

Kompilator TypeScript akan memasukkan informasi jenis design-time menggunakan decorator @Reflect.metadata. Anda dapat menganggapnya setara dengan TypeScript berikut:

ts
class Line {
private _p0: Point;
private _p1: Point;
@validate
@Reflect.metadata("design:type", Point)
set p0(value: Point) {
this._p0 = value;
}
get p0() {
return this._p0;
}
@validate
@Reflect.metadata("design:type", Point)
set p1(value: Point) {
this._p1 = value;
}
get p1() {
return this._p1;
}
}

CATATAN  Decorator metadata adalah fitur experimental dan mungkin dapat menyebabkan gangguan pada rilis di masa mendatang.

The TypeScript docs are an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
RBRon Buckton  (54)
BDPBurhanudin Dwi Prakoso  (8)
MHMohamed Hegazy  (3)
OTOrta Therox  (1)
RCRyan Cavanaugh  (1)
15+

Last updated: 16 Des 2024