Subscribe on changes!

'toRaw' ignores '__v_skip', which could cause errors when adding a custom proxy to a reactive object, despite marking the Proxy with 'markRaw'

avatar
Sep 20th 2021

Version

3.2.12

Reproduction link

sfc.vuejs.org/

Steps to reproduce

  1. Make a reactive object.
  2. Make a Proxy where the getter always returns another Proxy.
  3. Mark Proxy as raw with markRaw.
  4. Add Proxy to reactive object.

What is expected?

No error.

What is actually happening?

toRaw gets stuck in a loop after adding the proxy to the reactive object.


Workaround: Dont use markRaw and use shallowReactive instead of reactive.

Possible solution proposals:

  1. Change toRaw to check for the property '__v_skip' and instantly return it's argument when the property is true.

  2. Use WeakMap to keep track of Reactive properties instead.

avatar
Sep 20th 2021

Actually, the proxy created by typesafe-i18n intercepts all property access which includes __v_skip so it will never be true. Essentially, its a proxy that is infinitely chainable on any property - and unfortunately this kind of proxies are fundamentally incompatible with how Vue's reactivity system currently works - where a lot of checks rely on checking hidden flags on the target objects.

This is in theory fixable by reverting to using WeakMaps for all the checks, but that creates performance issues when there are too many objects being tracked (instead of storing the flags on the objects themselves).

The simple around though is by wrapping the proxy inside a plain object with markRaw:

reactiveObject.newProperty = markRaw({
  nested: i18nObject("en", {}, {})
});
avatar
Sep 20th 2021

Although WeakMaps has worse performance, I believe there should be an exception for objects passed to markRaw. Isn't the main use case of markRaw that you want an object not change by Vue?

I realize that this is a niche use case, but I believe that using markRaw is niche too. Therefore, I don't believe the average app will call markRaw that often, so performance shouldn't be too much of an issue imo.

My problem with the workaround is that I want to place the translation property directly on app.config.globalProperties (I made it explicitly reactive). Using {{ foo.nested.... }} in the templates is a bit akward imo.

Another alternative to WeakMaps could be an overload to the reactive function, where it can takes string(s) of the property path(s) that shouldn't be mutated. This way, raws can still be tracked using the object property of the parent and the objects marked as raw aren't mutated anymore.

In any case, I just wanted to say that I love Vue and especially what you guys have done to make Vue3 as awesome as it is 🙂