'Why Vue3 Composition API - watch’s option deep do not work in reactive, but work in reactive getter?
- Arrow function's deep option is work.
- Raw Reactive Object's deep option is not work.
It looks like a bug, why watch’s option deep do not work in reactive, but work in reactive getter?
1.Code
setup() {
const state = reactive({
id: 1,
attrs: {
date: new Date()
}
})
watch(state, (val, prevVal) => {
console.log('non-deep', val, prevVal)
})
watch(
() => state,
(val, prevVal) => {
console.log('non-deep getter', val, prevVal)
}
)
watch(
state,
(val, prevVal) => {
console.log('deep', val, prevVal)
},
{ deep: true }
)
watch(
() => state,
(val, prevVal) => {
console.log('deep getter', val, prevVal)
},
{ deep: true }
)
const changeDate = () => (state.attrs.date = new Date())
return {
state,
changeDate
}
}
2.The console logs
non-deep Proxy {id: 1, attrs: {…}} Proxy {id: 1, attrs: {…}}
deep Proxy {id: 1, attrs: {…}} Proxy {id: 1, attrs: {…}}
deep getter Proxy {id: 1, attrs: {…}} Proxy {id: 1, attrs: {…}}
Solution 1:[1]
The Vue official document already points out that:
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
The Answer:
- When you pass a reactive object (not a getter) into the
watch
function, thedeep
option will be ALWAYStrue
. - When you pass a getter return a reactive object into the
watch
function, thedeep
should respect your input options and works as expected.
Extended:
Extended for cases: Ref
, ComputedRef
This is the code inside the watch
function from the Vue source code:
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(isReactive)
getter = () =>
source.map(s => {
...
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
}
...
}
As we can see:
- The reactive source will be wrapped in a getter and the deep option will be always
true
- The Ref | ComputedRef source will be wrapped in a getter that returns
source.value
and thedeep
option is the value you passed in. Note that, the value ofComputedRef
will be replaced each calculation so thewatch
function will be trigger after any change ofComputedRef
regardlessdeep
option - If you use a getter (function) as the source, Vue will use that getter directly and the
deep
option is the value you passed in
You might ask what kind of my variables? Here is the answer:
const reactiveState = reactive({
id: 1,
});
// reactiveState is reactive
const refState = ref({
id: 1,
});
// refState is a Ref
// refState.value is reactive
const computedState = computed(() => {
return {
id: reactiveState.id,
};
});
// computedState is a ComputedRef
// computedState.value is just a raw object without reactive.
// so calling watch(() => computedState.value, callback) will NOT work
See this code example to verify what I said (you should open the dev tool to see the console logs)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Duannx |