I have Vue component with prop named product
, it is an object with a bunch of properties. And it changes often.
export default {
props: {
product: {
type: Object,
default: () => {},
},
},
watch: {
'product.p1'() {
this.loadData()
},
'product.p2'() {
this.loadData()
},
},
methods: {
loadData() {
doApiRequest(this.product.p1, this.product.p2)
}
},
}
Component should load new data when only properties p1
and p2
of product
are changed.
The one approach is to watch the whole product and load data when it is changed. But it produces unnecessary requests because p1
and p2
may not have changed.
Another idea is to watch product.p1
and product.p2
, and call same function to load data in each watcher.
But it may happen that both p1
and p2
changed in new version of product, it would trigger 2 calls.
Will it be a good solution to use debounced function for data load?
Or rather use single watcher for the whole product
and compare new p1
and p2
stringified with their old stringified versions to determine if data loading should be triggered?
CodePudding user response:
There are several approaches to this, each with pros and cons.
One simple approach I do is to use a watch function that accesses each of the properties you want to watch and then returns a new empty object. Vue knows product.p1
and product.p2
were accessed in the watch function, so it will re-execute it any time either of those properties change. Then, by returning a new empty object instance from the watch function, Vue will trigger the watch handler because the watch function returned a new value (and thus what is being watched "changed").
created() {
this.$watch(() => {
// Touch the properties we want to watch
this.product.p1;
this.product.p2;
// Return a new value so Vue calls the handler when
// this function is re-executed
return {};
}, () => {
// p1 or p2 changed
})
}
Pros:
- You don't have to stringify anything.
- You don't have to debounce the watch handler function.
Cons:
- You can't track the previous values of
p1
andp2
. - Take care if
this.product
could ever be null/undefined. - It will always trigger when
p1
orp2
are changed; even ifp1
andp2
are set back to their previous values before the next micro task (i.e.$nextTick()
); but this is unlikely to be a problem in most cases. - You need to use
this.$watch()
. If you want to use thewatch
option instead then you need to watch a computed property.
Some of these cons apply to other approaches anyway.
A more compact version would be:
this.$watch(
() => (this.product.p1, this.product.p2, {}),
() => {
// changed
}
})
CodePudding user response:
As some of other developers said you can use computed properties to monitor the changing of product.p1
or product.p2
or both of them and then calling loadData()
method only once in each case. Here is the code of a hypothetical product.vue
component:
<template>
<div>
this is product compo
</div>
</template>
<script>
export default {
name: "product",
watch: {
p1p2: function(newVal, oldVal) {
this.loadData();
}
},
props: {
productProp: {
type: Object,
default: () => {},
},
},
computed: {
p1p2: function() {
return this.productProp.p1 this.productProp.p2;
}
},
methods: {
loadData() {
console.log("load data method");
}
},
}
</script>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
I renamed the prop that it received to productProp and watched for a computed property called p1p2
in that. I supposed that the values of data are in String format (but if they are not you could convert them). Actually p1p2 is the concatenation of productProp.p1
and productProp.p2
. So changing one or both of them could fire the loadData() method. Here is the code of a parent component that passes data to product.vue
:
<template>
<section>
<product :productProp = "dataObj"></product>
<div class="d-flex justify-space-between mt-4">
<v-btn @click="changeP1()">change p1</v-btn>
<v-btn @click="changeP2()">change p2</v-btn>
<v-btn @click="changeBoth()">change both</v-btn>
<v-btn @click="changeOthers()">change others</v-btn>
</div>
</section>
</template>
<script>
import product from "../components/product";
export default {
name: 'parentCompo',
data () {
return {
dataObj: {
p1: "name1",
p2: "name2",
p3: "name3",
p4: "name4"
}
}
},
components: {
product
},
methods: {
changeP1: function() {
if (this.dataObj.p1 == "name1") {
this.dataObj.p1 = "product1"
} else {
this.dataObj.p1 = "name1"
}
},
changeP2: function() {
if (this.dataObj.p2 == "name2") {
this.dataObj.p2 = "product2"
} else {
this.dataObj.p2 = "name2"
}
},
changeBoth: function() {
if (this.dataObj.p2 == "name2") {
this.dataObj.p2 = "product2"
} else {
this.dataObj.p2 = "name2"
}
if (this.dataObj.p1 == "name1") {
this.dataObj.p1 = "product1"
} else {
this.dataObj.p1 = "name1"
}
},
changeOthers: function() {
if (this.dataObj.p3 == "name3") {
this.dataObj.p3 = "product3"
} else {
this.dataObj.p3 = "name3"
}
}
},
}
</script>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
You can test the change buttons to see that by changing dataObj.p1
or dataObj.p2
or both of them the loadData() method only called once and by changing others it is not called.