Reputation: 154494
How can I write a type-safe class decorator that correctly handles static methods?
Specifically, this decorator will work for classes without static methods:
interface ITypeOf<T> {
new(...args: any[]): T
}
function decorate<T>(cls: ITypeOf<T>): ITypeOf<T> {
return cls
}
But ITypeOf
doesn't consider static attributes, so the compile fails with this error when it's applied to a class with static attributes:
@decorate
class Foo {
static bar() {
return 42
}
}
Yields the error:
Unable to resolve signature of class decorator when called as an expression.
Type 'ITypeOf<Foo>' is not assignable to type 'typeof Foo'.
Property 'bar' is missing in type 'ITypeOf<Foo>'.
function decorate<T>(cls: ITypeOf<T>): ITypeOf<T>
Here's a working example: http://www.typescriptlang.org/play/#src=interface…
How can I write a type-safe class decorator that works for classes with static members?
Upvotes: 3
Views: 2688
Reputation: 51629
How can I write a type-safe class decorator that works for classes with static members?
It depends on what are you going to do inside the decorator.
You can simply have decorator
completely generic like this:
function decorate<C>(cls: C) {
return cls;
}
However this does not capture any information about C
, so you can't do much with cls
inside decorate
.
Or you can have separate generic parameters, one for the "instance side" of cls
and another one for the "static side":
interface ITypeOf<T> {
new(...args: any[]): T
}
function decorate<I, S extends ITypeOf<I>>(cls: S): S {
return cls
}
@decorate
class Foo {
static bar() {
return 42
}
}
Upvotes: 0
Reputation: 249636
The return type of the decorator must be compatible with the class type it is decorating as the return value of the decorator will replace the class at runtime. To achieve this, we must use a generic parameter representing the class itself, that will be both the type of the parameter and the return type. We will thus be returning a class with the same structure as the input class (including the statics) and the compiler will be satisfied:
function decorate<TCtor extends ITypeOf<any>>(cls: TCtor): TCtor {
return cls
}
@decorate
class Foo {
static bar() {
return 42
}
}
To add restrictions on the class to be decorated we can be more restrictive of the type parameter to ITypeOf
:
// decorated class must have a field x: numeber
function decorate<TCtor extends ITypeOf<{ x: number }>>(cls: TCtor): TCtor {
return cls
}
@decorate // error no instance x
class Foo { }
@decorate // ok
class Boo { x!: number }
We can also add restrictions as to static members
// decorated class must have a static field x: numeber
function decorate<TCtor extends ITypeOf<any> & { x: number }>(cls: TCtor): TCtor {
return cls
}
@decorate // error no static x
class Foo { }
@decorate // ok
class Boo { static x: number }
Upvotes: 5