Vue reactivity doesn't work with ES private fields
Vue version
3.2.45
Link to minimal reproduction
https://codepen.io/taras-turchenko/pen/JjZvBep
Steps to reproduce
- Create a class with es private fields
- Make it reactive
- Try accessing a method that uses this private field
What is expected?
Reactivity works
What is actually happening?
It causes an error:
Uncaught TypeError: Cannot read private member #name from an object whose class did not declare it
When code is processed by babel
TypeError: attempted to get private field on non-instance at _classExtractFieldDescriptor
System Info
System:
OS: macOS 13.0.1
CPU: (8) arm64 Apple M1
Memory: 76.59 MB / 16.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.12.0 - ~/.nvm/versions/node/v18.12.0/bin/node
Yarn: 1.22.19 - ~/.nvm/versions/node/v18.12.0/bin/yarn
npm: 8.19.2 - ~/.nvm/versions/node/v18.12.0/bin/npm
Browsers:
Chrome: 107.0.5304.121
Firefox: 101.0
Safari: 16.1
npmPackages:
vue: 3.2.45 => 3.2.45
Any additional comments?
We want to migrate a front-end project from Vue2 to Vue3 and have many classes and OOP code. We've been using private fields to encapsulate our logic for a year. Now we process private fields by babel but it reproduces as well in native & babel processed code
Stackoverflow topic: https://stackoverflow.com/a/68731963
ES6 proxies simply don't support private fields which is a huge PITA., but consequently this is unfortunately a wontfix, or rather "can't fix".
FWIW, we always recommend to use POJOs for reactive state. We need to be more explicit about this specific hard limitation in the docs, though.
https://github.com/vuejs/docs/issues/2112 brought up some path worth exploring, maybe, so I'll reopen this for the moment.
Not really an option after. all.
FWIW, you can hack around the limitation with things like:
class X {
#a = 'secret'
#b = 'secret'
getPropA = () => this.#a
constructor() {
this._self = markRaw({ self: this })
}
get getA() { return this.getPropA() }
get getB() { return this._self.self.#b }
}
ES6 proxies simply don't support private fields which is a huge PITA.
@LinusBorg Can please elaborate on that? What's does "simply don't support" mean? The Proxy doesn't have access to private members but that's by design -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding
FWIW, we always recommend to use POJOs for reactive state. We need to be more explicit about this specific hard limitation in the docs, though
The docs I've read explicitly recommend adding methods for stuff:
To ensure the state-mutating logic is centralized like the state itself, it is recommended to define methods on the store with names that express the intention of the actions -- https://vuejs.org/guide/scaling-up/state-management.html#simple-state-management-with-reactivity-api
It seems like a fairly reasonable recommendation. The alternative is a very different organization of code when you've got semi complex state and procedures needed to access and mutate it. It's like Redux style state+actions.
@LinusBorg Can please elaborate on that? What's does "simply don't support" mean? The Proxy doesn't have access to private members but that's by design -- developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding
Sure its by design - a design that means we can't classes that contain native private fields with proxies - which means such classes don't (reliably) work with our reactivity system.
The docs I've read explicitly recommend adding methods for stuff:
The docs you link here still use a plain object (just one with methods on it), not a class, and certainly not a class with private fields
The docs you link here still use a plain object (just one with methods on it), not a class, and certainly not a class with private fields.
Oh OK by POJO I thought you meant something basically something JSON serializable. I'm probably using "POJO" wrong. But, isn't a class instance just an object with methods on it? By "POJO" you mean I can't use some object constructed from a class constructor?
But, isn't a class instance just an object with methods on it?
Not really. Since ES6, classes are a distinct thing in JS. One of those distinctions is: classes support native private fields.
By "POJO" you mean I can't use some object constructed from a class constructor?
I'ts recommended not to. Though using a class usually works fine as long as the classes are simple enough and only manage their own property-based state.
One thing that simply does not work - rhe thing this issue is about - is that classes with private properties dont play nice with Proxies. So they cant be used with Vue's Reactivity.