Error when property value is an instance of a class using private class fields
Vue version
3.2.47
Link to minimal reproduction
https://codepen.io/leaverou/pen/MWPpEQg?editors=1011
Steps to reproduce
Visit testcase
What is expected?
1 in Result pane
What is actually happening?
Error
System Info
(Not relevant)
Any additional comments?
Apparently proxies break private class fields (MDN, non-Vue testcase). This means that every time an instance of a class using private fields is used as the value of a Vue property, things break (see testcase). Since there is a workaround that can be used in the proxy traps, I'd consider this a Vue bug.
Vue was mentioned several times in https://github.com/tc39/proposal-class-fields/issues/106 , so I'd be surprised if this is not known, but I couldn't find an existing issue, so opening this just in case.
import { createApp, shallowRef, toRaw, markRaw } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
class Foo {
#bar = 1;
get bar() {
return this.#bar;
}
}
createApp({
data() {
return {
foo: shallowRef(new Foo()) // or toRaw, but it's loses reactivity ðŸ«
}
},
mounted() {
console.log(this.foo)
}
}).mount("#app");
Vue transfer foo
into Proxy Object so this
keyword will refers to Proxy Object instead of normal Object
Option API:
Proxy { foo: object }
Composition API:
Proxy { foo: RefImpl }
In order for Vue to properly track property access inside object / class instance methods, this
inside these methods must also be the wrapped proxy. However, this
being the proxy instead of the original object prevents us from reading private fields.
If we use the original object as this
inside methods, then it would break reactivity tracking for non-private properties, which is the more common case. In other words, there isn't a proper fix for this, so it's a hard limitation based on the way Vue's reactivity system works.
In general, we recommend using plain objects over class instances as data sources. If you really need encapsulation and only expose certain reactive state, consider using Composition API and using Composables.
Link to minimal reproduction
What is expected?
1 in Result pane
Any additional comments?
Apparently proxies break private class fields (MDN, non-Vue testcase). This means that every time an instance of a class using private fields is used as the value of a Vue property, things break (see testcase). Since there is a workaround that can be used in the proxy traps, I'd consider this a Vue bug.
Vue was mentioned several times in tc39/proposal-class-fields#106 , so I'd be surprised if this is not known, but I couldn't find an existing issue, so opening this just in case.
I assume you need immutable object properties (private ones in JS case) for well-known reasons. There is a workaround for Vue. In the private property accessor (a JS getter) unwrap the proxy and access the property on the raw object. For your codepen the update would be:
import { createApp, isProxy, toRaw } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
// ...
get bar() {
return isProxy(this) ? toRaw(this).#bar : null; // Just an example. Treat the values as per concrete use case.
}
This works with both Options and Composition APIs.
Since there is a workaround that can be used in the proxy traps, I'd consider this a Vue bug.
@LeaVerou Can you clarify what workaround you meant here. Thanks.
@WhereJuly Your workaround requires coupling Vue with the class using the private properties, however these may be developed entirely separately. E.g. in my case, I was handling Color
objects with Vue, and Color.js is a library that has nothing to do with Vue whatsoever (I’ve since filed an issue and it moved away from private properties, but not all libraries can afford to do that).
@LeaVerou True, my workaround requires coupling with Vue. For code assumed to work along with Vue it is fine, though for code trying to be Vue-agnostic this is the problem. Could be probably solved by some Vue-aware adapter object nearby the original object to still keep properties private. Though this approach applicability and usefulness depends a lot on a paricular use case.
However in your initial post you mentioned there is the other workaround that can be used in the proxy traps. It sounds really intersting. Could you explain what it is or give a reference? Thanks.