Closed frehner closed 4 months ago
So, the value passed into the decorator is the value being decorated, and the value accessed via access.get
and access.set
is the final value. For instance:
class Foo {
@foo
@bar
accessor baz = 123;
}
The final value of baz
is essentially foo(bar({ get, set }))
, where the get
and set
functions are the original getter/setter on the class, right?
Well, if the bar
decorator wanted to access the value via the final decorated getter, there isn't a way it could do that without access
. It has no knowledge of @foo
, it doesn't ever get access to those functions, so there's no way for it to provide access to the final value, it can only expose access to the intermediate value.
access.get
is essentially like calling Reflect.get(obj, key)
for this value, but with one key difference - it supports private fields/methods, which cannot be handled via Reflect.get
. This essentially prevents ordering issues, where the last decorator is fighting to be able to have access in cases where that's necessary.
So, the value passed into the decorator is the value being decorated, and the value accessed via
access.get
andaccess.set
is the final value. For instance:class Foo { @foo @bar accessor baz = 123; }
The final value of
baz
is essentiallyfoo(bar({ get, set }))
, where theget
andset
functions are the original getter/setter on the class, right?
👍
Well, if the
bar
decorator wanted to access the value via the final decorated getter, there isn't a way it could do that withoutaccess
. It has no knowledge of@foo
, it doesn't ever get access to those functions, so there's no way for it to provide access to the final value, it can only expose access to the intermediate value.
access.get
is essentially like callingReflect.get(obj, key)
for this value, but with one key difference - it supports private fields/methods, which cannot be handled viaReflect.get
. This essentially prevents ordering issues, where the last decorator is fighting to be able to have access in cases where that's necessary.
Hm, I'm having a harder time understanding this part, sorry! Using the example you wrote up before, what would change if instead of set.call(this, removed)
we did context.access.set.call(this, removed)
?
I'm attempting to run something very similar to that using get
in a babel repl, but it fails. (I'm not sure if I'm doing something wrong, or if it's just not a complete implementation yet).
So, if you called context.access.set.call(this, removed)
, it should fail, because that will cause an infinite loop. It's basically the same as doing this:
class Foo {
set bar(value) {
this.bar = value;
}
}
It's the same as call foo.bar = value
on an instance. The main reason for this API is to provide access to other code that may want to read the value of a decorated element, for instance if you want to expose access to a private element to check its state, but ONLY in tests. Does that make sense?
Ah ok, interesting. So access shouldn't be used in getters/setters, but could be passed to other code/places to get the final value. I think I understand now.
Thanks for being patient with me and walking me through that. I appreciate it.
No problem! I like digging in and explaining things, it helps to show where the mental model is at and what we need to focus on for documentation and education 😄
So, the value passed into the decorator is the value being decorated, and the value accessed via
access.get
andaccess.set
is the final value. For instance:class Foo { @foo @bar accessor baz = 123; }
The final value of
baz
is essentiallyfoo(bar({ get, set }))
, where theget
andset
functions are the original getter/setter on the class, right?
Something that has been throwing me off is that the behavior of this is somewhat surprising in practice; the body of @bar
will execute before the body of @foo
(as expected).
However, the getters/setters of @foo
will execute before the getters/setters of @bar
. In other words, the getters/setters execute top-to-bottom (or left-to-right). Example:
class Temp {
@removeNums @removeCaps accessor thing
}
function removeNums(value, context){
console.log('nums body')
return {
set(val) {
console.log('nums setter', val)
const removed = val.replace(/[0-9]/g, '')
value.set.call(this, removed)
},
get() {
console.log('nums getter')
return value.get.call(this)
}
}
}
function removeCaps(value, context){
console.log('caps body')
return {
set(val) {
console.log('caps setter', val)
const removed = val.replace(/[A-Z]/g, '')
value.set.call(this, removed)
},
get() {
console.log('caps getter')
return value.get.call(this)
}
}
}
const temp = new Temp()
temp.thing = '1Ab'
console.log(temp.thing)
will log
caps body
nums body
nums setter 1Ab
caps setter Ab
nums getter
caps getter
b
Yup, that's correct! Decoration happens in reverse order, which results in @foo
wrapping @bar
, which wraps the property. The result is necessarily the inverse, same as if you decorated a function or a method.
Forgive my ignorance here, but hoping to learn more.
It's slightly unclear to me from reading the README what, if any, difference there is between a decorated auto-accessor's
value.get
/value.set
andcontext.access.get
/context.access.set
methods.https://github.com/tc39/proposal-decorators?tab=readme-ov-file#class-auto-accessors
Is there any difference? Is there a preference for one over the other? I've looked over the spec changes but I'm not quite yet to a place where I can make sense of it yet. Thanks! 🙂