I am trying to create two-way binding but the problem is that I need to know what exactly changes the model ref
: parent or child component.
When using watch
it simply catches all changes and I can't find the way to distinguish the source of change.
<script setup>
// Parent.vue
const doc = ref('');
setTimeout(() => {
// Update this ref AND trigger watch in Child.vue
doc.value = 'new value from parent';
}, 2000);
</script>
<template>
<Child v-model:doc="doc" />
</template>
<script setup>
// Child.vue
const doc = defineModel('doc');
setTimeout(() => {
// DO NOT TRIGGER WATCH ON THIS ONE!
// But still update parent `doc` ref
doc.value = 'new inner value';
}, 3000);
watch(doc, newValue => {
// Catch `new value from parent` but not `new inner value`
});
</script>
Use a flag in the child:
<script setup>
import {watch} from 'vue';
const doc = defineModel('doc');
let meChangingDoc = false;
setTimeout(() => {
// DO NOT TRIGGER WATCH ON THIS ONE!
// But still update parent `doc` ref
meChangingDoc = true;
doc.value = 'new inner value';
}, 3000);
watch(doc, newValue => {
if(meChangingDoc) {
meChangingDoc = false;
console.log('child changed doc');
return;
}
console.log('parent changed doc')
// Catch `new value from parent` but not `new inner value`
});
</script>
You can use a generic function also:
SyncRef.js
import {watch} from 'vue';
export default function syncRef(source){
let syncing = false;
return new Proxy(source, {
get(_, prop){
if(prop === 'watchSource') return function(cb){
return watch(source, (...args) => {
if(syncing) {syncing = false; return; }
cb(...args);
});
};
return Reflect.get(...arguments);
},
set(_, prop, val){
if(prop === 'value'){
syncing = true;
source.value = val;
return true;
}
return Reflect.set(...arguments);
}
});
}
Usage:
<script setup>
import syncRef from './SyncRef';
const model = defineModel('doc');
const doc = syncRef(model);
doc.watchSource(newValue => {
// Catch `new value from parent` but not `new inner value`
});
</script>