Subscribe on changes!

Vue reactivity doesn't work with ES private fields

avatar
Nov 29th 2022

Vue version

3.2.45

Link to minimal reproduction

https://codepen.io/taras-turchenko/pen/JjZvBep

Steps to reproduce

  1. Create a class with es private fields
  2. Make it reactive
  3. 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

avatar
Nov 29th 2022

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.

avatar
Nov 29th 2022

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 }
}

PLayground

avatar
Jul 13th 2023

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

avatar
Jul 13th 2023

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.

avatar
Jul 13th 2023

@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.

avatar
Jul 13th 2023

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

avatar
Jul 13th 2023

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?

avatar
Jul 13th 2023

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.