Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to use ES6 private fields and Vue 3's `reactive` together?

I have a class using ES6 private fields and public getters that I need to be reactive using Vue 3's composition API. Currently, my set up looks something like this:

//store.ts
class Store {
  #userName?: string
  get userName() {
    if(this.#userName !== undefined) return this.#userName
    throw new Error('Cannot get userName before it is defined')
  }

  setUserName(newUserName: string) {
    this.#userName = newUserName
  }
}

const store = reactive(new Store())

export { store }

This store instance is then provided to components through the provide/inject API, and used like so

<template>
  <span> {{ formattedUserName }} </span>
</template>

<script lang="ts">
import { defineComponent, toRefs, inject } from 'vue'

export default defineComponent({
  setup(){
    const store = inject('storeKey')
    const formattedUserName = doSomeThings(store.userName)
    return { formattedUserName  }
})

But when I try to do this I get this error: Cannot read private member #userName from an object whose class did not declare it

Could someone explain why this is, and if there's a way around it? I know TypeScript has the private keyword, but if possible I'd like to use private fields since they actually enforce the privacy at runtime.

Thanks!

Edit: After a bit more research, I've found the answer to the "why" part of my question. Vue 3 uses Proxy to track reactivity, which unfortunately does not work with private fields. If there is any way around this I would still love to know.

like image 281
Connor Dooley Avatar asked Jan 28 '26 21:01

Connor Dooley


1 Answers

So short answer: There is no simple way to still use private fields and Proxy together. Private fields and Proxy, as they are implemented now (August 10th, 2021), are fundamentally incompatible. The second link in my original post has a ton of discussion on why that is and whether it should be changed, but without a new proposal it looks like this is just the way things are.

Here's how I got a store together that has

  • compile time privacy for its members
  • reactive, readonly (also at compile time) getters for those members that guarantee the returned value is not undefined
  • specific setters that can also have side effects
import {ref, reactive, computed} from 'vue'

class Store {
  private internal_userName: Ref<string | undefined> = ref(undefined)
  readonly userName = computed((): string => {
    if(this.internal_userName.value !== undefined) return this.internal_userName.value
    throw new Error('Cannot access userName before it is defined')
  })

  setUserName(newUserName: string) {
    // do side effects here, such as setting localStorage keys
    this.internal_userName.value = newUserName
  }
}

const store = reactive(new Store())

//do things to export or provide your store here
like image 83
Connor Dooley Avatar answered Jan 30 '26 11:01

Connor Dooley



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!