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"," ": true}}
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 factoryreturn 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@gx
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 (f ∘ g)(x) yang sama dengan f(g(x)).
Dengan demikian, langkah-langkah berikut dilakukan saat mengevaluasi beberapa decorator pada satu deklarasi di TypeScript:
- Ekspresi untuk setiap decorator dievaluasi dari atas ke bawah.
- 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(): evaluatedg(): evaluatedg(): calledf(): called
Evaluasi Decorator
Ada urutan yang jelas tentang bagaimana decorator diterapkan ke berbagai deklarasi yang ada di dalam kelas:
- Parameter Decorators, diikuti oleh Method, Accessor, atau Property Decorators diterapkan untuk setiap anggota instance.
- Parameter Decorators, diikuti oleh Method, Accessor, atau Property Decorators diterapkan untuk setiap anggota statis.
- Parameter Dekorator diterapkan untuk konstruktor.
- 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
@sealedclass 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";};}@classDecoratorclass 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:
- Bisa memiliki fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
- Nama anggota.
- The Property Descriptor untuk anggota.
CATATAN Property Descriptor akan menjadi
undefined
jika target skripmu dibawahES5
.
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
danset
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 aksesorget
danset
, bukan setiap deklarasi secara terpisah.
Ekspresi untuk decorator pengakses akan dipanggil sebagai fungsi pada waktu proses, dengan tiga argumen berikut:
- Bisa memiliki fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
- Nama anggota.
- The Property Descriptor untuk anggota.
CATATAN Property Descriptor akan menjadi
undefined
, jika target skripmu dibawahES5
.
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:
- Dapat berupa fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
- 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 pustakareflect-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:
- Dapat berupa fungsi konstruktor kelas untuk anggota statis, atau prototipe kelas untuk anggota instance.
- Nama anggota.
- 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;}@validategreet(@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 pustakareflect-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:
{" ": {" ": "ES5"," ": true," ": true}}
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;@validateset p0(value: Point) {this._p0 = value;}get p0() {return this._p0;}@validateset 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.