如何通过decorator向typescript类添加可绑定属性或任何其他decorator?
我想使用装饰器而不是继承来扩展类的行为和数据。我还想对新创建的属性或方法应用decorator。有没有这样的例子?这可能吗如何通过decorator向typescript类添加可绑定属性或任何其他decorator?,typescript,aurelia,typescript-decorator,Typescript,Aurelia,Typescript Decorator,我想使用装饰器而不是继承来扩展类的行为和数据。我还想对新创建的属性或方法应用decorator。有没有这样的例子?这可能吗 设想一组类,其中一些类共享名为span的可绑定属性。另外,根据span属性,还有一个名为leftMargin的计算属性。实现这一点的理想方法是使用名为@addSpan的装饰器装饰类,例如,该装饰器将可绑定属性和计算属性添加到类中。TL;DR:滚动到底部,查看完整的代码片段。 使用decorator添加可绑定属性,从而实现组合而不是继承是可能的,尽管这并不像人们想象的那么容易
设想一组类,其中一些类共享名为
span
的可绑定属性。另外,根据span
属性,还有一个名为leftMargin
的计算属性。实现这一点的理想方法是使用名为@addSpan
的装饰器装饰类,例如,该装饰器将可绑定属性和计算属性添加到类中。TL;DR:滚动到底部,查看完整的代码片段。
使用decorator添加可绑定属性,从而实现组合而不是继承是可能的,尽管这并不像人们想象的那么容易。下面是如何做到这一点
方法
让我们假设我们有多个组件来计算一个数字的平方。为此,需要两个属性:一个以基数作为输入(我们将此属性称为baseNumber
),另一个提供计算结果(我们将此属性称为result
)。baseNumber
-属性需要是可绑定的,以便我们可以传入一个值。result
-属性需要依赖于baseNumber
-属性,因为如果输入发生更改,结果肯定会更改
我们也不希望在我们的属性中反复执行计算。我们也不能在这里使用继承,因为在编写本文时,在Aurelia中继承可绑定和计算属性是不可能的。对于我们的应用程序体系结构来说,这也可能不是最好的选择
因此,最后我们希望使用装饰器将请求的功能添加到我们的类中:
import { addSquare } from './add-square';
@addSquare
export class FooCustomElement {
// FooCustomElement now should have
// @bindable baseNumber: number;
// @computedFrom('baseNumber') get result(): number {
// return this.baseNumber * this.baseNumber;
//}
// without us even implementing it!
}
简单的解决方案
如果您只需要在类上放置一个可绑定属性,事情很简单。您可以手动调用bindable
装饰器。这是可行的,因为引擎盖下的装饰实际上只不过是功能而已。因此,要获得简单的可绑定属性,以下代码就足够了:
import { bindable } from 'aurelia-framework';
export function<T extends Function> addSquare(target: T) {
bindable({
name: 'baseNumber'
})(target);
}
当然,您也可以使用字符串插值语法绑定以显示此属性的值:${baseNumber}
挑战
然而,挑战在于添加另一个属性,该属性使用baseNumber
-属性提供的值进行计算。对于正确的实现,我们需要访问baseNumber
-属性的值。现在,像我们的addSquare
-decorator这样的decorator不会在类的实例化期间进行计算,而是在类的声明期间进行计算。不幸的是,在这个阶段,我们根本没有可能从中读取所需值的实例
(这并不妨碍我们首先使用bindable
-decorator,因为这也是一个decorator函数。因此,它期望在类声明期间应用并相应地实现)
Aurelia中由-decorator计算的是另一回事。我们不能像使用bindable
-decorator那样使用它,因为它假定类实例上已经存在修饰属性
因此,从我们新创建的可绑定属性实现计算属性似乎是一件非常不可能的事情,对吗
幸运的是,有一种简单的方法可以从装饰器中访问装饰类的实例:扩展其构造函数。在扩展构造函数中,我们可以添加一个计算属性,该属性可以访问修饰类的实例成员
创建计算属性
在展示所有部分如何组合在一起之前,让我解释一下如何将计算属性手动添加到类的构造函数中:
// Define a property descriptor that has a getter that calculates the
// square number of the baseNumber-property.
let resultPropertyDescriptor = {
get: () => {
return this.baseNumber * this.baseNumber;
}
}
// Define a property named 'result' on our object instance using the property
// descriptor we created previously.
Object.defineProperty(this, 'result', resultPropertyDescriptor);
// Finally tell aurelia that this property is being computed from the
// baseNumber property. For this we can manually invoke the function
// defining the computedFrom decorator.
// The function accepts three arguments, but only the third one is actually
// used in the decorator, so there's no need to pass the first two ones.
computedFrom('baseNumber')(undefined, undefined, resultPropertyDescriptor);
完全解决方案
要将所有内容结合在一起,我们需要完成以下几个步骤:
- 创建一个接受类构造函数的decorator函数
- 将名为
baseNumber
的可绑定属性添加到类中
- 扩展构造函数以添加我们自己的名为
result
以下代码段定义了一个名为addSquare
的装饰器,该装饰器满足上述要求:
import { bindable, computedFrom } from 'aurelia-framework';
export function addSquare<TConstructor extends Function>(target: TConstructor) {
// Store the original target for later use
var original = target;
// Define a helper function that helps us to extend the constructor
// of the decorated class.
function construct(constructor, args) {
// This actually extends the constructor, by adding new behavior
// before invoking the original constructor with passing the current
// scope into it.
var extendedConstructor: any = function() {
// Here's the code for adding a computed property
let resultPropertyDescriptor = {
get: () => {
return this.baseNumber * this.baseNumber;
}
}
Object.defineProperty(this, 'result', resultPropertyDescriptor);
computedFrom('baseNumber')(target, 'result', resultPropertyDescriptor);
// Here we invoke the old constructor.
return constructor.apply(this, args);
}
// Do not forget to set the prototype of the extended constructor
// to the original one, because otherwise we would miss properties
// of the original class.
extendedConstructor.prototype = constructor.prototype;
// Invoke the new constructor and return the value. Mind you: We're still
// inside a helper function. This code won't get executed until the real
// instanciation of the class!
return new extendedConstructor();
}
// Now create a function that invokes our helper function, by passing the
// original constructor and its arguments into it.
var newConstructor: any = function(...args) {
return construct(original, args);
}
// And again make sure the prototype is being set correctly.
newConstructor.prototype = original.prototype;
// Now we add the bindable property to the newly created class, much
// as we would do it by writing @bindinable on a property in the definition
// of the class.
bindable({
name: 'baseNumber',
})(newConstructor);
// Our directive returns the new constructor so instead of invoking the
// original one, javascript will now use the extended one and thus enrich
// the object with our desired new properties.
return newConstructor;
}
虽然这是可行的,但这既不是类型安全的,也不好看。只要再努力一点,您就可以使代码看起来很好,并且类型安全。您只需实现一个提供新属性的接口:
export interface IHasSquare {
baseNumber: number;
result: number;
}
不过,简单地将接口分配给我们的类是行不通的:记住,新创建的属性只存在于运行时。要使用该接口,我们可以在类上实现一个属性,该属性返回this
,但之前将其强制转换为IHasSquare
。然而,为了诱使编译器允许这一点,我们需要首先将this
转换为any
:
get hasSquare(): IHasSquare {
return <IHasSquare>(<any>this);
}
get hasSquare():IHasSquare{
返回(用于指出将此强制转换到它没有实现的接口实际上可以工作!您能给出一些您希望它如何工作的示例吗?我真的无法理解原因。@thebluefox:Aurelia对超类型上的可绑定属性不起作用。建议使用decorators。因此我想对类wi进行注释。)这是一个可以添加其他可绑定属性的装饰器。假设我有3个组件,它们提供分页功能,但方式不同,但基本逻辑是相同的,并且一些可绑定属性在所有组件中都是相同的。@thebluefox这里还有一个问题(我;)这基本上有相同的问题:还有一个github问题在讨论:@thebluefox请查看question@epitka我说对了吗?
export interface IHasSquare {
baseNumber: number;
result: number;
}
get hasSquare(): IHasSquare {
return <IHasSquare>(<any>this);
}