[英]How to implement a typescript decorator?
TypeScript 1.5 now has decorators . TypeScript 1.5现在有装饰器。
Could someone provide a simple example demonstrating the proper way to implement a decorator and describe what the arguments in the possible valid decorator signatures mean?有人可以提供一个简单的例子来展示实现装饰器的正确方法并描述可能的有效装饰器签名中的参数的含义吗?
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;
Additionally, are there any best practice considerations that should be kept in mind while implementing a decorator?此外,在实现装饰器时是否应牢记任何最佳实践注意事项?
I ended up playing around with decorators and decided to document what I figured out for anyone who wants to take advantage of this before any documentation comes out.我最终玩弄了装饰器,并决定在任何文档出现之前为任何想要利用它的人记录我的想法。 Please feel free to edit this if you see any mistakes.
如果您发现任何错误,请随时编辑。
A valid decorator should be:
一个有效的装饰器应该是:
- Assignable to one of the Decorator types (
ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator
).可分配给装饰器类型之一(
ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator
)。- Return a value (in the case of class decorators and method decorator) that is assignable to the decorated value.
返回一个可分配给装饰值的值(在类装饰器和方法装饰器的情况下)。
Implementation parameters:实现参数:
target
: The prototype of the class ( Object
). target
:类的原型( Object
)。propertyKey
: The name of the method ( string
| symbol
). propertyKey
:方法的名称( string
| symbol
)。descriptor
: A TypedPropertyDescriptor
— If you're unfamiliar with a descriptor's keys, I would recommend reading about it in this documentation on Object.defineProperty
(it's the third parameter). descriptor
: A TypedPropertyDescriptor
— 如果您不熟悉描述符的键,我建议您阅读有关Object.defineProperty
文档(这是第三个参数)。Use:用:
class MyClass {
@log
myMethod(arg: string) {
return "Message -- " + arg;
}
}
Implementation:执行:
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;
}
Input:输入:
new MyClass().myMethod("testing");
Output:输出:
The method args are: ["testing"]
方法参数是:[“测试”]
The return value is: Message -- testing
返回值为:Message——测试
Notes:笔记:
this
will not be the instance's if you do.this
上下文将不是实例的上下文。@enumerable(false)
and @log
at the same time (Example: Bad vs Good )@enumerable(false)
和@log
类的东西(例如: Bad vs Good )TypedPropertyDescriptor
can be used to restrict what method signatures ( Method Example ) or accessor signatures ( Accessor Example ) the decorator can be put on.TypedPropertyDescriptor
的类型参数可用于限制装饰器可以放置的方法签名( 方法示例)或访问器签名(访问器示例)。 When using arguments, you must declare a function with the decorator's parameters then return a function with the signature of the example without arguments.使用参数时,您必须声明一个带有装饰器参数的函数,然后返回一个带有不带参数示例签名的函数。
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;
};
}
Similar to a method decorator with some differences:类似于方法装饰器,但有一些不同:
target
parameter is the constructor function itself and not the prototype.target
参数是构造函数本身而不是原型。@isTestable
class MyClass {}
Implementation parameter:实现参数:
target
: The class the decorator is declared on ( TFunction extends Function
). target
:装饰器声明的类( TFunction extends Function
)。 Example use : Using the metadata api to store information on a class. 使用示例:使用元数据 api 存储类的信息。
class MyClass {
@serialize
name: string;
}
Implementation parameters:实现参数:
target
: The prototype of the class ( Object
). target
:类的原型( Object
)。propertyKey
: The name of the property ( string
| symbol
). propertyKey
: propertyKey
的名称( string
| symbol
)。 Example use : Creating a @serialize("serializedName")
decorator and adding the property name to a list of properties to serialize.使用示例:创建
@serialize("serializedName")
装饰器并将属性名称添加到要序列化的属性列表中。
class MyClass {
myMethod(@myDecorator myParameter: string) {}
}
Implementation parameters:实现参数:
target
: The prototype of the class ( Function
—it seems Function
doesn't work anymore. You should use any
or Object
here now in order to use the decorator within any class. Or specify the class type(s) you want to restrict it to) target
:类的原型( Function
似乎Function
不再起作用。你现在应该在这里使用any
或Object
以便在任何类中使用装饰器。或者指定你想要限制它的类类型到)propertyKey
: The name of the method ( string
| symbol
). propertyKey
:方法的名称( string
| symbol
)。parameterIndex
: The index of parameter in the list of the function's parameters ( number
). parameterIndex
:函数参数列表中parameterIndex
的索引( number
)。One important thing I don't see in the other answers:我在其他答案中没有看到的一件重要事情:
If we want to customize how a decorator is applied to a declaration, we can write a decorator factory.
如果我们想自定义装饰器如何应用于声明,我们可以编写一个装饰器工厂。 A Decorator Factory is simply a function that returns the expression that will be called by the decorator at 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 { ... }
Check the TypeScript handbook Decorators chapter .查看 TypeScript 手册装饰器章节。
class Foo {
@consoleLogger
Boo(name:string) { return "Hello, " + name }
}
You could implement something that logs each call to the console:您可以实现将每次调用记录到控制台的内容:
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 decorators allow extra functionality to be added on a class. TS 装饰器允许在类上添加额外的功能。 The class is altered by decorators at declare time , before any instance of the class is created.
在创建该类的任何实例之前,该类在声明时由装饰器更改。
Decorators are declared with an @
sign, for example @metadata
.装饰
@metadata
@
符号声明,例如@metadata
。 TS will now search for a corresponding metadata function and will automatically supply it with sevaral argument which vary on what is exactly decorated (eg class or class property get different arguments) TS 现在将搜索相应的元数据函数,并自动为它提供 sevaral 参数,这些参数因具体装饰的内容而异(例如,类或类属性获得不同的参数)
These parameters are supplied in the decorator function:这些参数在装饰器函数中提供:
{writable: true, enumerable: false, configurable: true, value: ƒ}
{writable: true, enumerable: false, configurable: true, value: ƒ}
Depending on the type of decorator 1-3 of these arguments are passed to the decorator function.根据装饰器的类型,将这些参数的 1-3 传递给装饰器函数。
The following decorators can be applied to a class and TS will evaluate them in the following order (following summation comes from TS docs):以下装饰器可以应用于类,TS 将按以下顺序评估它们(以下总和来自 TS 文档):
The best way to understand them better is via examples.更好地理解它们的最好方法是通过示例。 Note that these example do need significant understanding of the TS language and concepts like
PropertyDescriptor
.请注意,这些示例确实需要对 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
decorators can be declared via decorators function or decorator factory functions.装饰器可以通过装饰器函数或装饰器工厂函数来声明。 There is a difference in syntax which is best explained via an example:
语法上有区别,最好通过一个例子来解释:
// 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
You could also decorate/enhance
new functionality to the original constructor in typescript(I used 3.9.7).您还可以在打字稿中
decorate/enhance
原始构造函数的新功能(我使用了 3.9.7)。 The below snippet wraps the original constructor to add prefix for name property.下面的代码片段包装了原始构造函数以添加 name 属性的前缀。 This happens when the class is
instantiated
instead when the class is declared
!当类被
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);
}
};
};
}
when the class is instantiated
the new constructor logic runs with original ctor logic -当类被
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.