Subscribe on changes!

toRaw keeps inner refs

avatar
Jan 9th 2022

Version

3.2.26

Reproduction link

sfc.vuejs.org/

Steps to reproduce

Documentation https://v3.vuejs.org/api/basic-reactivity.html#toraw says that toRaw can be used as an "escape hatch" from Vue reactivity.

It's not, apparently

Examples:

  1. toRaw(ref('aValue')) gives a ref object instead of 'aValue' string
  2. toRaw({a: 'a', b: ref('b'), c: ['ca', ref('cb')]}) gives an object, keeping all the inner refs
  3. toRaw(reactive({a: 'a', b: ref('b'), c: ['ca', ref('cb')]})) gives an object, keeping all the inner refs

What is expected?

I expect toRaw results in a value completely free from Vue reactivity and all the inner refs be unwrapped.

What is actually happening?

toRaw doesn't unwrap inner refs, hence the result is not completely free from Vue reactivity and ref values need to be accessed using .value

avatar
Jan 9th 2022

This is working as intended. toRaw()'s use case is to give you the original object that a reactive() proxy is wrapping. If that original plain object contains refs, they were part of the original and therefore will be in the result of `toRaw(). It's a very cheap operation as it only needs to do one lookup of the original root object.

The main use case we have in mind for this utility is to provide access to the raw source object in edge cases where object identity matters or you need to mutate the object without tracking.

To do what you want/expect, toRaw() would have to either:

  • mutate the original object to replace the ref with the ref's value (which would also break the reactive proxy)
  • return not the original object, but a cloned object containing unwrapped values in place of the refs, which would break object identity.

Both would be a breaking change with current intended behavior, and be vastly more expensive as they require a recursive walk over the object's property. So what you are looking for would likely be something like

const nonreactiveClone = _.cloneDeep(reactiveProxy)

... or similar custom function that walks the object recursively and unwraps refs where it encounters them.

  • If you feel like such a utility should be part of Vue, make a use case for it in an RFC discussion.
  • If you feel like the docs could be improved, my team mates in the docs repo would be happy to hear about this, preferably with a suggestion about improved wording.
avatar
Jan 9th 2022

Thanks a lot for thorough explanation! I understand this now. Agree that it's better not to touch toRaw()'s current behaviour.

Thanks for the RFC suggestion. I might open a discussion there

avatar
Jan 9th 2022

https://github.com/vuejs/rfcs/discussions/366

This RFC discussion was about fundamentally supporting toRaw for all vue reactive types. the good old "escape hatch" statement/trap in docs was why I also opened a fruit less RFCdiscussion. hope this save you the effort.

avatar
Jan 9th 2022

@lidlanca thank you 👍 Good to know about it. I'll definitely consider that rfc if I decide to open another one

avatar
Jan 11th 2022

I found a package which clones and unrefs a value deeply it https://github.com/DanHulton/vue-deepunref At the moment, it handles only primitives, pure objects and arrays. Neither Array or Map.

I think we can start from there and see if there is really a need in such a thing https://github.com/DanHulton/vue-deepunref

avatar
Jan 11th 2022

JSON.parse(JSON.stringify(o)) to quickly get a clone of simple data free from reactivity ( exclude Set, Map, Function, ,references/circular references, etc )

which for most use cases is fine.