[英]How to implement a typescript decorator?
有人可以提供一個簡單的例子來展示實現裝飾器的正確方法並描述可能的有效裝飾器簽名中的參數的含義嗎?
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;
此外,在實現裝飾器時是否應牢記任何最佳實踐注意事項?
我最終玩弄了裝飾器,並決定在任何文檔出現之前為任何想要利用它的人記錄我的想法。 如果您發現任何錯誤,請隨時編輯。
一個有效的裝飾器應該是:
- 可分配給裝飾器類型之一(
ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator
)。- 返回一個可分配給裝飾值的值(在類裝飾器和方法裝飾器的情況下)。
實現參數:
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");
輸出:
方法參數是:[“測試”]
返回值為:Message——測試
筆記:
this
上下文將不是實例的上下文。@enumerable(false)
和@log
類的東西(例如: Bad vs Good )TypedPropertyDescriptor
的類型參數可用於限制裝飾器可以放置的方法簽名( 方法示例)或訪問器簽名(訪問器示例)。使用參數時,您必須聲明一個帶有裝飾器參數的函數,然后返回一個帶有不帶參數示例簽名的函數。
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
參數是構造函數本身而不是原型。@isTestable
class MyClass {}
實現參數:
target
:裝飾器聲明的類( TFunction extends Function
)。使用示例:使用元數據 api 存儲類的信息。
class MyClass {
@serialize
name: string;
}
實現參數:
target
:類的原型( Object
)。propertyKey
: propertyKey
的名稱( string
| symbol
)。 使用示例:創建@serialize("serializedName")
裝飾器並將屬性名稱添加到要序列化的屬性列表中。
class MyClass {
myMethod(@myDecorator myParameter: string) {}
}
實現參數:
target
:類的原型( Function
似乎Function
不再起作用。你現在應該在這里使用any
或Object
以便在任何類中使用裝飾器。或者指定你想要限制它的類類型到)propertyKey
:方法的名稱( string
| symbol
)。parameterIndex
:函數參數列表中parameterIndex
的索引( number
)。我在其他答案中沒有看到的一件重要事情:
如果我們想自定義裝飾器如何應用於聲明,我們可以編寫一個裝飾器工廠。 裝飾器工廠只是一個返回表達式的函數,該表達式將在運行時被裝飾器調用。
// 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 手冊裝飾器章節。
class Foo {
@consoleLogger
Boo(name:string) { 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;
}
}
TS 裝飾器允許在類上添加額外的功能。 在創建該類的任何實例之前,該類在聲明時由裝飾器更改。
裝飾@metadata
@
符號聲明,例如@metadata
。 TS 現在將搜索相應的元數據函數,並自動為它提供 sevaral 參數,這些參數因具體裝飾的內容而異(例如,類或類屬性獲得不同的參數)
這些參數在裝飾器函數中提供:
{writable: true, enumerable: false, configurable: true, value: ƒ}
根據裝飾器的類型,將這些參數的 1-3 傳遞給裝飾器函數。
以下裝飾器可以應用於類,TS 將按以下順序評估它們(以下總和來自 TS 文檔):
更好地理解它們的最好方法是通過示例。 請注意,這些示例確實需要對 TS 語言和PropertyDescriptor
等概念有深入的了解。
function overwrite(
target: myClass,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('I get logged when the class is declared!')
// desciptor.value refers to the actual function fo the class
// we are changing it to another function which straight up
// overrides the other function
descriptor.value = function () {
return 'newValue method overwritten'
}
}
function enhance(
target: myClass,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const oldFunc = descriptor.value;
// desciptor.value refers to the actual function fo the class
// we are changing it to another function which calls the old
// function and does some extra stuff
descriptor.value = function (...args: any[]) {
console.log('log before');
const returnValue = oldFunc.apply(this, args)
console.log('log after');
return returnValue;
}
}
class myClass {
// here is the decorator applied
@overwrite
foo() {
return 'oldValue';
}
// here is the decorator applied
@enhance
bar() {
return 'oldValueBar';
}
}
const instance =new myClass()
console.log(instance.foo())
console.log(instance.bar())
// The following gets logged in this order:
//I get logged when the class is declared!
// newValue method overwritten
// log before
// log after
// oldValueBar
function metaData(
target: myClass,
propertyKey: string,
// A Property Descriptor is not provided as an argument to a property decorator due to
// how property decorators are initialized in TypeScript.
) {
console.log('Execute your custom code here')
console.log(propertyKey)
}
class myClass {
@metaData
foo = 5
}
// The following gets logged in this order:
// Execute your custom code here
// foo
function seal(
constructor: Function,
) {
// Object.seal() does the following:
// Prevents the modification of attributes of
// existing properties, and prevents the addition
// of new properties
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@seal
class myClass {
bar?: any;
foo = 5
}
myClass.prototype.bar = 10;
// The following error will be thrown:
// Uncaught TypeError: Cannot add property bar,
// object is not extensible
裝飾器可以通過裝飾器函數或裝飾器工廠函數來聲明。 語法上有區別,最好通過一個例子來解釋:
// Returns a decorator function, we can return any function
// based on argument if we want
function decoratorFactory(arg: string) {
return function decorator(
target: myClass,
propertyKey: string,
) {
console.log(`Log arg ${arg} in decorator factory`);
}
}
// Define a decorator function directly
function decorator(
target: myClass,
propertyKey: string,
) {
console.log('Standard argument');
}
class myClass {
// Note the parentheses and optional arguments
// in the decorator factory
@decoratorFactory('myArgument')
foo = 'foo';
// No parentheses or arguments
@decorator
bar = 'bar';
}
// The following gets logged in this order:
// Log arg myArgument in decorator factory
// Standard argument
您還可以在打字稿中decorate/enhance
原始構造函數的新功能(我使用了 3.9.7)。 下面的代碼片段包裝了原始構造函數以添加 name 屬性的前綴。 當類被instantiated
而不是在declared
類時會發生這種情況!
//Decorator function
function Prefixer(prefix: string) {
return function<T extends { new (...args: any[]): {name: string} }>(
originalCtor: T
) {
return class extends originalCtor {
constructor(..._: any[]) {
super();
this.name = `${prefix}.${this.name.toUpperCase()}`;
console.log(this.name);
}
};
};
}
當類被instantiated
,新的構造函數邏輯與原始 ctor 邏輯一起運行 -
@Prefixer('Mr')
class Person {
name = 'MBB';
constructor() {
console.log('original ctr logic here!');
}
}
const pers = new Person();
console.log(pers); //Mr.MBB
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.