"watch" on a ref that wraps an object, doesn't work like using reactive api

Consider creating a reactive object/state by both reactive and ref:

const refObj = ref({top:{inner:0}})
const reactObj = reactive({top:{inner:0}})

Creating a watch using Composition API over reactObj, will automatically watch updates deeply, means all changes to all nested properties, regardless of the depth of reactObj, will be watched. This is because nested objects in a reactive object, are reactive object as well, so it completely makes sense.

But for refObj it doesn’t work as expected. If we create a watch over it, and change value of any property on wrapped object, watch’s callback is not invoked. So none of below statements make watcher to be invoked:

refObj.value.top = {inner: 10}

but if we assign new value to refObj.value, then watch’s callback will be invoked
refObj.value = 'x'; // works
refObj.value = {newObj:2}; // works

Based on my understanding, both refObj and reactObj are reactive objects and thus watcher must work in both scenarios. Why it is like this? is it by design or I’m missing something?

Best Regards

I think you should look at it like this:

First we have reactive(). It’s great, and works as you expect. The only bad part of it is that you must pass it an object. Sometimes you have a primitive value, like a number, and to make that reactive using reactive(), you must encapsulate it in an object to make it work. This makes little sense.

So, for primitive values (strings, numbers, booleans, etc.) you need to be reactive, Vue added ref(). You can pass a primitive value to it, but due to limitations of how JavaScript works, you must .value to get the reactivity part working.

So, simply use reactive() instead of ref() when you need to have an object reactive.

Thanks for your explanation. I’m afraid it doesn’t quite answer my main question

Types returned by both ref() and reactive() are reactive objects with different structures simply put

My question is why watch behaves differently in regard to these different reactive types? I want to know is it by design (vue checks the refImpl type explicitly) or I haven’t understood concept deep enough?

Hmm… The ref() docs says:

If an object is assigned as a ref’s value, the object is made deeply reactive with reactive().

I read this as ref() should (to the extent possible) function the same as reactive() when you pass it an object. So from that point of view you are correct that it’s surprising that ref() is not deeply reactive in watchers. It could be that they didn’t think of this when implementing watchers (i.e. a bug), but it could also be that it’s too complex to support (a ref()'s value type is unknown in advance, and it can change while the app is running, so not straight forward to setup and track all reactive dependencies).

So, I’m afraid I can’t answer your question (:

a ref() 's value type is unknown in advance, and it can change while the app is running

Great point here. I guess so that this is the main reason that they haven’t implemented it for ref() . As is stated in my question, when we assign new value to ref.value, watcher’s callback is called

Thanks for your help