Vue Deep Watchers

When you call watch() directly on a reactive object, it will implicitly create a deep watcher - the callback will be triggered on all nested mutations:

const obj = reactive({ count: 0, person: { name: 'eric' } })
 
watch(obj, (newValue, oldValue) => {
  console.log('newValue', newValue) // { count: 1, person: { name: 'eric' } }
  console.log('oldValue', oldValue) // { count: 1, person: { name: 'eric' } }
})
 
obj.count++

This should be differentiated with a getter that returns a reactive object - in the latter case, the callback will only fire if the getter returns a different object. The following will trigger watch callback to run as count is changed.

const obj = reactive({ count: 0, person: { name: 'eric' } })
 
watch(
  () => obj.count,
  (newValue, oldValue) => {
    console.log('newValue', newValue) // 1
    console.log('oldValue', oldValue) // 0
  }
)
 
obj.count++

The following will not trigger watch callback to run as obj.person is still the same object:

const obj = reactive({ count: 0, person: { name: 'eric' } })
 
watch(
  () => obj.person,
  (newValue, oldValue) => {
    console.log('newValue', newValue) 
    console.log('oldValue', oldValue) 
  }
)
 
obj.person.name = 'peter'

You can, however, force the above into a deep watcher by explicitly using the deep option:

const obj = reactive({ count: 0, person: { name: 'eric' } })
 
watch(
  () => obj.person,
  (newValue, oldValue) => {
    console.log('newValue', newValue) // peter
    console.log('oldValue', oldValue) // peter
  },
  { deep: true }
)
 
obj.person.name = 'peter'

Reference

Watchers | Vue.js