Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when property value is an instance of a class using private class fields #8149

Closed
LeaVerou opened this issue Apr 24, 2023 · 7 comments
Closed

Comments

@LeaVerou
Copy link

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

@sadeghbarati
Copy link

sadeghbarati commented Apr 24, 2023

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 }

@sadeghbarati
Copy link

https://vuejs.org/guide/essentials/reactivity-fundamentals.html#reactive-proxy-vs-original

@sadeghbarati
Copy link

#2981

@yyx990803
Copy link
Member

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.

@WhereJuly
Copy link

WhereJuly commented Jul 17, 2023

Link to minimal reproduction

https://codepen.io/leaverou/pen/MWPpEQg?editors=1011

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.

@LeaVerou
Copy link
Author

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

@WhereJuly
Copy link

WhereJuly commented Jul 18, 2023

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants