I have a component which keeps a local partial copy of an Pinia storage data.
<template>
<h3>Order idx {{ idx }}: sum = {{ localSum }}, store sum = {{ getOrderSum() }}</h3>
<input type="number" v-model="localSum" />
<button @click="updateSum">Save</button>
</template>
<script setup>
import { useCounterStore } from '../store/counter'
import { watchEffect, ref, defineProps, watch } from 'vue'
const props = defineProps({
idx: 0
})
const store = useCounterStore()
const localSum = ref(0)
function getOrderSum() {
return store.getOrderIdxSumMap[props.idx]
}
function updateSum() {
store.setOrderSum(props.idx, localSum.value)
}
watch(
() => getOrderSum(),
(newValue) => {
console.log('i am updated')
localSum.value = newValue
}, {
immediate: true,
}
)
/*
watchEffect(() => {
console.log('i am updated')
localSum.value = getOrderSum()
})
*/
</script>
Whenever external data changes the local copy should update. Using watchEffect
instead of watch
causes components with modified and unsaved data to lose user input.
watchEffect
behaviour description 1:
i am updated
twice within console.watchEffect
behaviour description 2:
Comment out watchEffect
and uncomment watch
. Now everything works just fine. Is it my misconseptions or a bug worth to be reported?
It is how watch
and watchEffect
supposed to work
In short, watch
and watchEffect
both tracks the change of their dependencies. When the dependencies changed they act like follow:
watch
recalculates its source value and then compares the oldValue
with newValue
. If oldValue
!== newValue
, it will trigger the callback.watchEffect
has no sources so it triggers the callback anywayIn your example the dependencies of both watch
and watchEffect
are store.getOrderIdxSumMap
and props.idx
. The source of watch
is the function () => getOrderSum()
.
So when the store.getOrderIdxSumMap
changed, watchEffect
will always trigger the callback but watch
will re-calculate the value of () => getOrderSum()
. If it changed too, watch
will trigger its callback
When Vue detects a dependency is changed?
Whenever you set a value for reactive data, Vue uses a function to decide if the value is changed as follows:
export const hasChanged = (value: any, oldValue: any): boolean =>
!Object.is(value, oldValue)
So setting the same value for a primitive type (number, string, boolean...) will not be considered as a value change