-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Allow member decorators to add both static and instance *extra* initializers. #465
Comments
I'm not so sure, I for one would eagerly be writing a function exposeReadonly(_privateThing, ctx) {
assert(ctx.isPrivate, "That thing is already public");
ctx.addStaticInitializer(function() {
const target = ctx.isStatic ? this : this.prototype;
Object.defineProperty(target, {
enumerable: false,
configurable: true,
get() {
return ctx.access.get();
},
});
});
return _privateThing;
}
class Lock {
// Exposed publically as readonly isLocked
@exposeReadonly #isLocked = false;
#unlock() {
// ...unlock steps
}
acquire() {
if (!this.#isLocked) {
// Internally the field is still writable
this.#isLocked = true;
return once(() => this.#unlock());
}
// add to queue for unlocking
}
}
const lock = new Lock();
lock.isLocked === false; // publically readable
lock.isLocked = true; // ERROR, can't write Although even if |
That's a scenario that https://github.com/tc39/proposal-grouped-and-auto-accessors (which builds on the class Lock {
accessor isLocked { get; #set; } = false;
acquire() {
if (!this.isLocked) {
this.#isLocked = true;
...
}
}
} |
The thing with this is it half defeats the point of having It'd be more useful in that proposal if this worked: accessor isLocked { get; #get; #set } so that one could do: class Lock {
accessor isLocked { get; #get; #set }
acquire() {
// still using private getter, so this method is still
// guaranteed to be consistent, rather than delegating to
// own-properties/subclasses for reading but not writing #isLocked
if (!this.#isLocked) {
this.#isLocked = true;
// ...
}
}
} |
This feature would indeed simplify things by not having us do the pair-with-class-decorator dance. I'm using that dance with current decorators to achieve something like the following without using @reactive
class Foo {
@signal foo = 123 // reactive "signal" using getter/setter without `accessor` feature
} The question is: should we just make this easier, since we can do it anyway? Maybe so! In either case, as Ron mentioned, fields are still applied over getters/setters (ugh, saying "accessors" is now confusing because it has double meaning, so I'm using "getters/setters" instead), so the end result is the same. |
I just wanted to chime in here and see where this is at. I was reviewing the latest TS 5.0 work and lamenting that I can't actually use the new decorators until I have some way to associate metadata with a class from an accessor. This proposal interests me because it not only gives me a way to handle metadata, but it also seems to fill a whole in the spec given the existence of static initialization blocks. Is there any chance of us amending the decorators spec to add this? |
@EisenbergEffect see https://github.com/tc39/proposal-decorator-metadata/ for the metadata proposal, which gates my team's usage of them as well. |
This capability may be even more relevant now that class C1 {
@((t, c) => c.addInitializer(() => console.log("A"))
static a() {}
@((t, c) => c.addInitializer(() => console.log("B"))
static b() {}
@((t, c) => c.addInitializer(() => console.log("C"))
static c() {}
}
class C2 {
@((t, c) => c.addInitializer(() => console.log("A"))
static a() {}
@((t, c) => c.addInitializer(() => console.log("B"))
static b = function() {};
@((t, c) => c.addInitializer(() => console.log("C"))
static c() {}
} Before pzuraq/ecma262#12, this printed The prior order matched developer intuition for decorators that aren't specific to the kind of element they decorate, but didn't match developer intuition for decorators that are specific to fields. The new order merely swaps which developer intuitions are and aren't met. While we could change "extra" initializer order to be based entirely on lexical document order (within a given placement), it may just be worthwhile to make this option completely configurable: type AddInitializerBucket =
| "class" // default for class decorators
| "static" // default for static method decorators
| "static-inline" // default for static field decorators
| "non-static" // default for non-static method decorators
| "non-static-inline" // default for non-static field decorators
;
interface DecoratorContext {
...
addInitializer(callback: Function, bucket?: AddInitializerBucket): void;
} |
The current proposal exposes a single
addInitializer
function on the context passed to a decorator. The callback provided is then invoked either as a staticExtraInitializer or an instanceExtraInitializer depending on whether the decorator is applied to astatic
or non-static
member of a class (or a classExtraInitializer for a class decorator).For example:
One benefit to this approach is that it further reduces the need for a spec-defined behavior for metadata. A method or field decorator could register a callback to run when staticExtraInitializers are run, providing access to the constructor/prototype to use as a key in a
WeakMap
.While this runs fairly close to the possible prototype-manipulation shenanigans that were frequently seen in implementations of stage-1/TS/Babel decorators (and frowned upon by engine implementers), I'd argue that its much more limited:
defineProperty
.accessor
provides a more convenient and better optimized approach to wrapping a "field" with additional functionality.cc: @pzuraq, @syg, @codehag
The text was updated successfully, but these errors were encountered: