Closed cnwangjie closed 1 year ago
While you cannot access the class during decoration, you can access it during initialization. This would look like the following:
export const eventHandler = <T extends AppEventTarget<any>>(
event: T,
) => {
return (_, { access, static: isStatic, addInitializer }) => {
addInitializer(function() {
const cls = isStatic ? this : this.constructor
event.registerHandler(async (ctx, payload) => {
const instance = new (cls as Constructor)(ctx)
const args = isStatic ? [ctx, payload] : [payload]
await access.get().apply(instance, args)
})
});
}
}
Note that addInitializer
will run when the class is defined for static
methods, but it will only run when the class is initialized for instance methods. So the while the original logic for this code would work for non-static methods, this new version would only work for static methods, until you actually create an instance. You could add an assertion that requires users to use static
to avoid this.
Besides using an initializer for static members, you can access the class in any decorator by sharing some state with a class decorator (later this will be easier with the decorator metadata add-on proposal), but note that if you modify the class shape (f.e. the instance or the class prototype) you may be undoing optimizations that the engine may have put in place:
const decoratedProps = []
const handledClasses = new Set()
function classDeco(decoratedClass) {
// Do something with decoratedProps, f.e. return a class that deletes own fields, and converts them into getters/setters (without using the new accessor keyword!)
const props = [...decoratedProps]
decoratedProps = []
return class extends decoratedClass {
constructor(...args) {
super(...args)
if (!handledClasses.has(decoratedClass)) {
handledClasses.add(decoratedClass)
for (const prop of props) {
const initialValue = this[prop]
delete this[prop]
createGetterSetter(decoratedClass.prototype, prop, initialValue)
}
}
}
}
}
function fieldDeco(_, { name }) {
decoratedProps.push(name)
}
@classDeco
class Mine {
@fieldDeco foo = 123 // Look ma, no accessor!
}
(Untested code, but you get the idea!)
Hi,
Just bringing my 2c to the issue, I'm currently in the process of migrating from legacy TypeScript decorators to the new spec, and this is indeed quite weird not to have this. One of our usage is a cache decorator that composes class name and function name to build a part of its cache key, the finalized API forces me to use a pattern such as this:
function cache() {
return function(target, context) {
let cachePrefix = '';
context.addInitializer(function () {
const className = context.static ? this.name : this.constructor.name;
cachePrefix = `${className}:${target.name}`;
});
return function handleCache(...args) {
const cacheKey = `${cachePrefix}:${computeKeyFromArgs(args)}`;
...
return result;
}
}
}
The previous version allowed the cachePrefix
to be pre-computed in a constant and was IMHO more idiomatic than this version. Am I doing it wrong or is this expected and unavoidable?
In stage 2 the first parameter is the class of the method when using a method decorator for a static method. So I can instantiate the class in the decorator. In stage 3 I can only know the method is a static method but cannot get the class.
Considering following cases:
I have an
eventHandler
decorator that is a helper to register the handler of app events.I implemented it by following code.
And I can use it on methods that are static or not.
But in stage 3 I cannot get the class.