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:
- Change first order data
- Click save
- You'll see
i am updated
twice within console.
watchEffect
behaviour description 2:
- Change the first order data
- Change the second order data
- Click save on the first order
- You'll see the second order changes lost
Comment out watchEffect
and uncomment watch
. Now everything works just fine. Is it my misconseptions or a bug worth to be reported?
CodePudding user response:
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 theoldValue
withnewValue
. IfoldValue
!==newValue
, it will trigger the callback.watchEffect
has no sources so it triggers the callback anyway
In 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