วิธีการใช้เครื่องมือตกแต่ง typescript


207

typescript 1.5ขณะนี้มีการตกแต่ง

มีคนให้ตัวอย่างง่ายๆที่แสดงให้เห็นถึงวิธีการที่เหมาะสมในการใช้งานมัณฑนากรและอธิบายว่าข้อโต้แย้งในลายเซ็นมัณฑนากรที่ถูกต้องที่เป็นไปได้หมายถึงอะไร?

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;

นอกจากนี้ยังมีข้อควรพิจารณาเกี่ยวกับการปฏิบัติที่ดีที่สุดที่ควรคำนึงถึงในขณะที่ใช้งานมัณฑนากรหรือไม่?


หมายเหตุถึงตัวเอง :-) ถ้าคุณต้องการที่จะแทรก@Injectableเข้าไปในมัณฑนากรดู
Anand Rockzz

ฉันขอแนะนำให้ดูตัวอย่างมากมายที่โครงการนี้มี มีผู้ตกแต่งหลายคน - บางคนเรียบง่ายมากและบางคนอาจจะเข้าใจยากกว่านี้เล็กน้อย: github.com/vlio20/utils-decorators
vlio20

คำตอบ:


396

ฉันลงเอยด้วยการตกแต่งด้วยนักตกแต่งและตัดสินใจทำเอกสารสิ่งที่ฉันคิดออกสำหรับใครก็ตามที่ต้องการใช้ประโยชน์จากสิ่งนี้ก่อนที่เอกสารจะออกมา โปรดแก้ไขหากคุณเห็นข้อผิดพลาดใด ๆ

คะแนนทั่วไป

  • มัณฑนากรจะถูกเรียกเมื่อมีการประกาศคลาส - ไม่ใช่เมื่อวัตถุนั้นถูกสร้างเป็นอินสแตนซ์
  • ผู้ตกแต่งหลายคนสามารถกำหนดได้ในคลาส / ทรัพย์สิน / วิธีการ / พารามิเตอร์เดียวกัน
  • ไม่อนุญาตให้ใช้นักตกแต่งในการก่อสร้าง

เครื่องมือตกแต่งที่ถูกต้องควรเป็น:

  1. กำหนดให้กับ Decorator ประเภทใดประเภทหนึ่ง ( ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator)
  2. ส่งคืนค่า (ในกรณีของผู้ตกแต่งคลาสและผู้ตกแต่งเมธอด) ที่สามารถกำหนดให้กับค่าที่ตกแต่งได้

การอ้างอิง


วิธีการ / มัณฑนากร Accessor อย่างเป็นทางการ

พารามิเตอร์การใช้งาน:

  • target: ต้นแบบของคลาส ( Object)
  • propertyKey: ชื่อของวิธีการ ( string| symbol)
  • descriptor: A TypedPropertyDescriptor- หากคุณไม่คุ้นเคยกับปุ่มของตัวให้คำแนะนำฉันขอแนะนำให้อ่านในเอกสารนี้ในวันที่Object.defineProperty(เป็นพารามิเตอร์ที่สาม)

ตัวอย่าง - ไม่มีอาร์กิวเมนต์

ใช้:

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

การดำเนินงาน:

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;
}

การป้อนข้อมูล:

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

เอาท์พุท:

วิธีการคือ: ["การทดสอบ"]

ค่าที่ส่งคืนคือ: ข้อความ - การทดสอบ

หมายเหตุ:

  • อย่าใช้ไวยากรณ์ลูกศรเมื่อตั้งค่าของ descriptor บริบทของthisจะไม่เป็นตัวอย่างถ้าคุณทำ
  • การแก้ไข descriptor ดั้งเดิมนั้นดีกว่าการเขียนทับตัวปัจจุบันโดยการคืน descriptor ใหม่ สิ่งนี้ช่วยให้คุณใช้ผู้ตกแต่งหลายคนที่แก้ไข descriptor โดยไม่เขียนทับสิ่งที่นักตกแต่งอื่นทำ การทำเช่นนี้ช่วยให้คุณสามารถใช้สิ่งที่ชอบ@enumerable(false)และ@logในเวลาเดียวกัน (ตัวอย่าง: Bad vs Good )
  • มีประโยชน์ : อาร์กิวเมนต์ชนิดของTypedPropertyDescriptorสามารถใช้เพื่อ จำกัด เมธอดลายเซ็นต์ ( ตัวอย่างเมธอด ) หรือลายเซ็น accessor ( ตัวอย่าง Accessor ) มัณฑนากรที่สามารถใส่ได้

ตัวอย่าง - ด้วยข้อโต้แย้ง (โรงงานมัณฑนากร)

เมื่อใช้อาร์กิวเมนต์คุณต้องประกาศฟังก์ชันด้วยพารามิเตอร์ของ decorator แล้วส่งคืนฟังก์ชันด้วยลายเซ็นของตัวอย่างโดยไม่มีอาร์กิวเมนต์

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;
    };
}

มัณฑนากรวิธีคงที่

คล้ายกับวิธีตกแต่งภายในที่มีความแตกต่าง:

  • targetพารามิเตอร์ของมันคือฟังก์ชั่นคอนสตรัคเตอร์เองไม่ใช่ต้นแบบ
  • descriptor ถูกกำหนดบนฟังก์ชัน constructor ไม่ใช่ต้นแบบ

มัณฑนากรระดับ

@isTestable
class MyClass {}

พารามิเตอร์การใช้งาน:

  • target: คลาสที่มัณฑนากรประกาศบน ( TFunction extends Function)

ตัวอย่างการใช้ : การใช้ metadata api เพื่อเก็บข้อมูลในคลาส


มัณฑนากรอสังหาริมทรัพย์

class MyClass {
    @serialize
    name: string;
}

พารามิเตอร์การใช้งาน:

  • target: ต้นแบบของคลาส ( Object)
  • propertyKey: ชื่อของคุณสมบัติ ( string| symbol)

ตัวอย่างการใช้ : การสร้าง@serialize("serializedName")มัณฑนากรและเพิ่มชื่อคุณสมบัติลงในรายการคุณสมบัติเพื่อทำให้เป็นอนุกรม


มัณฑนากรพารามิเตอร์

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

พารามิเตอร์การใช้งาน:

  • target: ต้นแบบของคลาส ( Function- ดูเหมือนว่าFunctionจะไม่ทำงานอีกต่อไปคุณควรใช้anyหรือObjectที่นี่ในตอนนี้เพื่อใช้มัณฑนากรภายในคลาสใด ๆ หรือระบุประเภทคลาสที่คุณต้องการ จำกัด ไว้)
  • propertyKey: ชื่อของวิธีการ ( string| symbol)
  • parameterIndex: ดัชนีของพารามิเตอร์ในรายการพารามิเตอร์ของฟังก์ชัน ( number)

ตัวอย่างง่ายๆ

ตัวอย่างโดยละเอียด


คุณรู้หรือไม่ว่าจะหาตัวอย่างตัวตกแต่งพารามิเตอร์ ฉันพยายามที่จะใช้งานอย่างใดอย่างหนึ่งโดยไม่ประสบความสำเร็จgithub.com/Microsoft/TypeScript/issues/ …
Remo H. Jansen

1
@OweRReLoaDeD ฉันได้เพิ่มตัวอย่างภายใต้ตัวประดับพารามิเตอร์ที่เพิ่งบันทึกสิ่งที่ส่งผ่านไปยังผู้ตกแต่ง ฉันไม่แน่ใจว่ามันมีประโยชน์หรือไม่ ฉันไม่สามารถนึกถึงตัวอย่างที่ดีได้ในขณะนี้
David Sherret

FYI ฉันรวบรวมและปรับแต่งข้อมูลนี้ใน github: github.com/arolson101/typescript-decorators
arolson101

- การตั้งค่าสถานะตัวบ่งชี้ประสบการณ์จะต้องมีการตั้งค่าเพื่อให้ตัวอย่างนี้ทำงาน
Trident D'Gao

ฉันสับสนเล็กน้อยเกี่ยวกับสิ่งtargetที่prototype of the classและkeyหมายถึงใครบางคนได้โปรดอธิบายรายละเอียดเกี่ยวกับเรื่องนี้?
Satej S

8

สิ่งสำคัญหนึ่งที่ฉันไม่เห็นในคำตอบอื่น ๆ :

โรงงานมัณฑนากร

หากเราต้องการปรับแต่งวิธีการใช้มัณฑนากรกับการประกาศเราสามารถเขียนโรงงานมัณฑนากร Decorator Factory เป็นเพียงฟังก์ชั่นที่ส่งกลับนิพจน์ที่จะถูกเรียกโดยมัณฑนากรเมื่อรันไทม์

// 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 { ... }

ตรวจสอบบทที่ตกแต่งด้วยมือของ TypeScript


4
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • เป้าหมาย: ต้นแบบของคลาสในกรณีข้างต้นคือ "Foo"
  • propertyKey: ชื่อของวิธีการที่เรียกว่าในกรณีข้างต้น "Boo"
  • descriptor: คำอธิบายของ object => มีคุณสมบัติค่าซึ่งในทางกลับกันคือฟังก์ชั่นของตัวเอง: function (ชื่อ) {return 'Hello' + name; }

คุณสามารถนำสิ่งที่บันทึกการโทรไปยังคอนโซลแต่ละครั้ง:

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;
  }     
}

1
มันเป็นงานที่ยากมากในการรวบรวมสิ่งนี้ด้วยการตั้งค่าคอมไพเลอร์ที่เข้มงวด
PandaWood

ในความเป็นจริงนี้เป็นสิ่งที่ผิดและไม่สามารถรวบรวมได้มีความจำเป็นต้องจัดฟันดัดโค้งโดยตรงหลังจากส่งคืน {value: ... } นี้ยังสามารถมองเห็นได้จากแหล่งที่มีศักยภาพของรหัสของคุณ - blog.wolksoftware.com/...
PandaWood
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.