Bagaimana cara menerapkan dekorator naskah?

207

TypeScript 1.5 sekarang memiliki dekorator .

Bisakah seseorang memberikan contoh sederhana yang menunjukkan cara yang tepat untuk menerapkan dekorator dan menjelaskan apa arti argumen dalam tanda tangan dekorator yang valid?

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Function, propertyKey: string | symbol, parameterIndex: number) => void;

Selain itu, adakah pertimbangan praktik terbaik yang harus diingat saat menerapkan dekorator?

David Sherret
sumber
Catatan untuk diri sendiri :-) jika Anda ingin menyuntikkan @Injectableke dekorator, lihat
Anand Rockzz
Saya akan menyarankan untuk melihat beberapa contoh yang dimiliki proyek ini. Ada beberapa dekorator - beberapa sangat sederhana dan beberapa mungkin sedikit lebih sulit untuk dipahami: github.com/vlio20/utils-decorators
vlio20

Jawaban:

396

Saya akhirnya bermain-main dengan dekorator dan memutuskan untuk mendokumentasikan apa yang saya temukan bagi siapa saja yang ingin memanfaatkan ini sebelum dokumentasi apa pun keluar. Silakan mengedit ini jika Anda melihat kesalahan.

Poin Umum

  • Dekorator dipanggil saat kelas dideklarasikan — bukan saat objek dibuat.
  • Beberapa dekorator dapat didefinisikan pada Kelas / Properti / Metode / Parameter yang sama.
  • Dekorator tidak diperbolehkan menggunakan konstruktor.

Penghias yang valid harus:

  1. Ditugaskan ke salah satu jenis Dekorator ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator).
  2. Kembalikan nilai (dalam hal dekorator kelas dan dekorator metode) yang dapat ditugaskan ke nilai yang didekorasi.

Referensi


Metode / Dekorator Pengakses Formal

Parameter implementasi:

  • target: Prototipe kelas ( Object).
  • propertyKey: Nama metode ( string| symbol).
  • descriptor: A TypedPropertyDescriptor- Jika Anda tidak terbiasa dengan kunci deskriptor, saya akan merekomendasikan membacanya di dokumentasi ini pada Object.defineProperty(ini adalah parameter ketiga).

Contoh - Tanpa Argumen

Menggunakan:

class MyClass {
    @log
    myMethod(arg: string) { 
        return "Message -- " + arg;
    }
}

Penerapan:

function log(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
    const originalMethod = descriptor.value; // save a reference to the original method

    // NOTE: Do not use arrow syntax here. Use a function expression in 
    // order to use the correct value of `this` in this method (see notes below)
    descriptor.value = function(...args: any[]) {
        // pre
        console.log("The method args are: " + JSON.stringify(args));
        // run and store result
        const result = originalMethod.apply(this, args);
        // post
        console.log("The return value is: " + result);
        // return the result of the original method (or modify it before returning)
        return result;
    };

    return descriptor;
}

Memasukkan:

new MyClass().myMethod("testing");

Keluaran:

Metode args adalah: ["testing"]

Nilai kembali adalah: Pesan - pengujian

Catatan:

  • Jangan gunakan sintaks panah saat mengatur nilai deskriptor. Konteks thistidak akan menjadi instance jika Anda melakukannya.
  • Lebih baik memodifikasi deskriptor asli daripada menimpa yang sekarang dengan mengembalikan deskriptor baru. Ini memungkinkan Anda untuk menggunakan beberapa dekorator yang mengedit deskriptor tanpa menimpa apa yang dilakukan dekorator lain. Melakukan ini memungkinkan Anda untuk menggunakan sesuatu seperti @enumerable(false)dan @logpada saat yang sama (Contoh: Buruk vs Baik )
  • Berguna : Argumen tipe dari TypedPropertyDescriptordapat digunakan untuk membatasi tanda tangan metode apa ( Contoh Metode ) atau tanda tangan accessor ( Contoh Accessor ) dekorator dapat memakai.

Contoh - Dengan Argumen (Pabrik Penghias)

Saat menggunakan argumen, Anda harus mendeklarasikan fungsi dengan parameter dekorator lalu mengembalikan fungsi dengan tanda tangan contoh tanpa argumen.

class MyClass {
    @enumerable(false)
    get prop() {
        return true;
    }
}

function enumerable(isEnumerable: boolean) {
    return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
        descriptor.enumerable = isEnumerable;
        return descriptor;
    };
}

Dekorator Metode Statis

Mirip dengan dekorator metode dengan beberapa perbedaan:

  • Its targetparameter adalah fungsi konstruktor sendiri dan tidak prototipe.
  • Deskriptor didefinisikan pada fungsi konstruktor dan bukan prototipe.

Dekorator Kelas

@isTestable
class MyClass {}

Parameter implementasi:

  • target: Kelas dekorator dideklarasikan pada ( TFunction extends Function).

Contoh penggunaan : Menggunakan api metadata untuk menyimpan informasi di kelas.


Dekorator Properti

class MyClass {
    @serialize
    name: string;
}

Parameter implementasi:

  • target: Prototipe kelas ( Object).
  • propertyKey: Nama properti ( string| symbol).

Contoh penggunaan : Membuat @serialize("serializedName")dekorator dan menambahkan nama properti ke daftar properti untuk diserialisasi.


Dekorator Parameter

class MyClass {
    myMethod(@myDecorator myParameter: string) {}
}

Parameter implementasi:

  • target: Prototipe kelas ( Function- sepertinya Functiontidak berfungsi lagi. Anda harus menggunakan anyatau di Objectsini sekarang untuk menggunakan dekorator dalam kelas apa pun. Atau tentukan jenis kelas yang ingin Anda batasi)
  • propertyKey: Nama metode ( string| symbol).
  • parameterIndex: Indeks parameter dalam daftar parameter fungsi ( number).

Contoh sederhana

Contoh terperinci

David Sherret
sumber
Apakah Anda tahu di mana menemukan contoh Dekorator Parameter? Saya sudah mencoba menerapkannya tanpa hasil github.com/Microsoft/TypeScript/issues/…
Remo H. Jansen
1
@ OweRReLoaDeD Saya menambahkan contoh di bawah dekorator parameter yang hanya mencatat apa yang diteruskan ke dekorator. Saya tidak yakin apakah itu membantu. Saya tidak bisa memikirkan contoh yang baik saat ini.
David Sherret
FYI Saya mengumpulkan dan mengubah informasi ini di github: github.com/arolson101/typescript-decorators
arolson101
--experimentalDecorators flag harus diatur agar contoh ini berfungsi
Trident D'Gao
Saya sedikit bingung dengan apa targetatau apa prototype of the classdan keymengacu pada, bisakah seseorang menjelaskan hal itu?
Satej S
8

Satu hal penting yang tidak saya lihat dalam jawaban lain:

Pabrik dekorator

Jika kita ingin menyesuaikan bagaimana dekorator diterapkan pada deklarasi, kita dapat menulis pabrik dekorator. Dekorator Pabrik hanyalah fungsi yang mengembalikan ekspresi yang akan dipanggil oleh dekorator saat runtime.

// This is a factory, returns one of ClassDecorator,
// PropertyDecorator, MethodDecorator, ParameterDecorator
function Entity(discriminator: string):  {
    return function(target) {
        // this is the decorator, in this case ClassDecorator.
    }
}

@Entity("cust")
export class MyCustomer { ... }

Periksa bab Dekorator buku pegangan TypeScript .

Ondra Žižka
sumber
4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • target: prototipe kelas dalam kasus di atas adalah "Foo"
  • propertyKey: nama metode yang disebut, dalam kasus di atas "Boo"
  • deskriptor: deskripsi objek => berisi nilai properti, yang pada gilirannya adalah fungsi itu sendiri: function (name) {return 'Hello' + name; }

Anda bisa menerapkan sesuatu yang mencatat setiap panggilan ke konsol:

function consoleLogger(target: Function, key:string, value:any) 
{
  return value: (...args: any[]) => 
  {
     var a = args.map(a => JSON.stringify(a)).join();
     var result = value.value.apply(this, args);
     var r = JSON.stringify(result);

     console.log('called method' + key + ' with args ' + a + ' returned result ' + r);

     return result;
  }     
}
Erik Lieben
sumber
1
Ini adalah tugas yang sulit untuk dikompilasi dengan pengaturan kompiler yang ketat
PandaWood
Sebenarnya, ini salah dan tidak bisa dikompilasi, perlu ada kurung kurawal langsung setelah kembali {value: ...}. Ini bahkan dapat dilihat dari sumber potensial kode Anda - blog.wolksoftware.com/…
PandaWood