admin管理员组文章数量:1125772
I'm trying to understand how to properly watch for some prop variation. I have a parent component (.vue files) that receive data from an ajax call, put the data inside an object and use it to render some child component through a v-for directive, below is a simplification of my implementation:
<template>
<div>
<player v-for="(item, key, index) in players"
:item="item"
:index="index"
:key="key"">
</player>
</div>
</template>
... then inside the <script>
tag:
data(){
return {
players: {}
},
created(){
let self = this;
this.$http.get('../serv/config/player.php').then((response) => {
let pls = response.body;
for (let p in pls) {
self.$set(self.players, p, pls[p]);
}
});
}
item objects are like this:
item:{
prop: value,
someOtherProp: {
nestedProp: nestedValue,
myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
},
}
Now, inside my child "player" component I'm trying to watch for any item's property variation and I use:
...
watch:{
'item.someOtherProp'(newVal){
//to work with changes in "myArray"
},
'item.prop'(newVal){
//to work with changes in prop
}
}
It works but it seems a bit tricky to me and I was wondering if this is the right way to do it. My goal is to perform some action every time prop
changes or myArray
gets new elements or there is some variation inside existing ones. Any suggestions will be appreciated.
I'm trying to understand how to properly watch for some prop variation. I have a parent component (.vue files) that receive data from an ajax call, put the data inside an object and use it to render some child component through a v-for directive, below is a simplification of my implementation:
<template>
<div>
<player v-for="(item, key, index) in players"
:item="item"
:index="index"
:key="key"">
</player>
</div>
</template>
... then inside the <script>
tag:
data(){
return {
players: {}
},
created(){
let self = this;
this.$http.get('../serv/config/player.php').then((response) => {
let pls = response.body;
for (let p in pls) {
self.$set(self.players, p, pls[p]);
}
});
}
item objects are like this:
item:{
prop: value,
someOtherProp: {
nestedProp: nestedValue,
myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
},
}
Now, inside my child "player" component I'm trying to watch for any item's property variation and I use:
...
watch:{
'item.someOtherProp'(newVal){
//to work with changes in "myArray"
},
'item.prop'(newVal){
//to work with changes in prop
}
}
It works but it seems a bit tricky to me and I was wondering if this is the right way to do it. My goal is to perform some action every time prop
changes or myArray
gets new elements or there is some variation inside existing ones. Any suggestions will be appreciated.
16 Answers
Reset to default 1093You can use a deep watcher for that:
watch: {
item: {
handler(val){
// do stuff
},
deep: true
}
}
This will now detect any changes to the objects in the item
array and additions to the array itself (when used with Vue.set). Here's a JSFiddle: http://jsfiddle.net/je2rw3rs/
EDIT
If you don't want to watch for every change on the top level object, and just want a less awkward syntax for watching nested objects directly, you can simply watch a computed
instead:
var vm = new Vue({
el: '#app',
computed: {
foo() {
return this.item.foo;
}
},
watch: {
foo() {
console.log('Foo Changed!');
}
},
data: {
item: {
foo: 'foo'
}
}
})
Here's the JSFiddle: http://jsfiddle.net/oa07r5fw/
Another good approach and one that is a bit more elegant is as follows:
watch:{
'item.someOtherProp': function (newVal, oldVal){
//to work with changes in someOtherProp
},
'item.prop': function(newVal, oldVal){
//to work with changes in prop
}
}
(I learned this approach from @peerbolte in the comment here)
VueJs deep watch in child objects
new Vue({
el: "#myElement",
data: {
entity: {
properties: []
}
},
watch: {
'entity.properties': {
handler: function (after, before) {
// Changes detected. Do work...
},
deep: true
}
}
});
Personally I prefer this clean implementation:
watch: {
myVariable: {
handler(newVal, oldVal){ // here having access to the new and old value
// do stuff
console.log(newVal, oldVal);
},
deep: true,
/*
Also very important the immediate in case you need it,
the callback will be called immediately after the start
of the observation
*/
immediate: true
}
}
How if you want to watch a property for a while and then to un-watch it?
Or to watch a library child component property?
You can use the "dynamic watcher":
this.$watch(
'object.property', //what you want to watch
(newVal, oldVal) => {
//execute your code here
}
)
The $watch
returns an unwatch function which will stop watching if it is called.
var unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()
Also you can use the deep
option:
this.$watch(
'someObject', () => {
//execute your code here
},
{ deep: true }
)
Please make sure to take a look to docs
Another way to add that I used to 'hack' this solution was to do this:
I set up a seperate computed
value that would simply return the nested object value.
data : function(){
return {
countries : {
UnitedStates : {
value: "hello world";
}.
},
};
},
computed : {
helperName : function(){
return this.countries.UnitedStates.value;
},
},
watch : {
helperName : function(newVal, oldVal){
// do this...
}
}
Tracking individual changed items in a list
If you want to watch all items in a list and know which item in the list changed, you can set up custom watchers on every item separately, like so:
var vm = new Vue({
data: {
list: [
{name: 'obj1 to watch'},
{name: 'obj2 to watch'},
],
},
methods: {
handleChange (newVal, oldVal) {
// Handle changes here!
// NOTE: For mutated objects, newVal and oldVal will be identical.
console.log(newVal);
},
},
created () {
this.list.forEach((val) => {
this.$watch(() => val, this.handleChange, {deep: true});
});
},
});
If your list isn't populated straight away (like in the original question), you can move the logic out of created
to wherever needed, e.g. inside the .then()
block.
Watching a changing list
If your list itself updates to have new or removed items, I've developed a useful pattern that "shallow" watches the list itself, and dynamically watches/unwatches items as the list changes:
// NOTE: This example uses Lodash (_.differenceBy and _.pull) to compare lists
// and remove list items. The same result could be achieved with lots of
// list.indexOf(...) if you need to avoid external libraries.
var vm = new Vue({
data: {
list: [
{name: 'obj1 to watch'},
{name: 'obj2 to watch'},
],
watchTracker: [],
},
methods: {
handleChange (newVal, oldVal) {
// Handle changes here!
console.log(newVal);
},
updateWatchers () {
// Helper function for comparing list items to the "watchTracker".
const getItem = (val) => val.item || val;
// Items that aren't already watched: watch and add to watched list.
_.differenceBy(this.list, this.watchTracker, getItem).forEach((item) => {
const unwatch = this.$watch(() => item, this.handleChange, {deep: true});
this.watchTracker.push({ item: item, unwatch: unwatch });
// Uncomment below if adding a new item to the list should count as a "change".
// this.handleChange(item);
});
// Items that no longer exist: unwatch and remove from the watched list.
_.differenceBy(this.watchTracker, this.list, getItem).forEach((watchObj) => {
watchObj.unwatch();
_.pull(this.watchTracker, watchObj);
// Optionally add any further cleanup in here for when items are removed.
});
},
},
watch: {
list () {
return this.updateWatchers();
},
},
created () {
return this.updateWatchers();
},
});
I've found it works this way too:
watch: {
"details.position"(newValue, oldValue) {
console.log("changes here")
}
},
data() {
return {
details: {
position: ""
}
}
}
Not seeing it mentioned here, but also possible to use the vue-property-decorator
pattern if you are extending your Vue
class.
import { Watch, Vue } from 'vue-property-decorator';
export default class SomeClass extends Vue {
...
@Watch('item.someOtherProp')
someOtherPropChange(newVal, oldVal) {
// do something
}
...
}
My problem with the accepted answer of using deep: true
, is that when deep-watching an array, I can't easily identify which element of the array contains the change. The only clear solution I've found is this answer, which explains how to make a component so you can watch each array element individually.
I used deep:true, but found the old and new value in the watched function was the same always. As an alternative to previous solutions I tried this, which will check any change in the whole object by transforming it to a string:
created() {
this.$watch(
() => JSON.stringify(this.object),
(newValue, oldValue) => {
//do your stuff
}
);
},
None of the answer for me was working. Actually if you want to watch on nested data with Components being called multiple times. So they are called with different props to identify them.
For example <MyComponent chart="chart1"/> <MyComponent chart="chart2"/>
My workaround is to create an addionnal vuex state variable, that I manually update to point to the property that was last updated.
Here is a Vuex.ts implementation example:
export default new Vuex.Store({
state: {
hovEpacTduList: {}, // a json of arrays to be shared by different components,
// for example hovEpacTduList["chart1"]=[2,6,9]
hovEpacTduListChangeForChart: "chart1" // to watch for latest update,
// here to access "chart1" update
},
mutations: {
setHovEpacTduList: (state, payload) => {
state.hovEpacTduListChangeForChart = payload.chart // we will watch hovEpacTduListChangeForChart
state.hovEpacTduList[payload.chart] = payload.list // instead of hovEpacTduList, which vuex cannot watch
},
}
On any Component function to update the store:
const payload = {chart:"chart1", list: [4,6,3]}
this.$store.commit('setHovEpacTduList', payload);
Now on any Component to get the update:
computed: {
hovEpacTduListChangeForChart() {
return this.$store.state.hovEpacTduListChangeForChart;
}
},
watch: {
hovEpacTduListChangeForChart(chart) {
if (chart === this.chart) // the component was created with chart as a prop <MyComponent chart="chart1"/>
console.log("Update! for", chart, this.$store.state.hovEpacTduList[chart]);
},
},
For anyone looking for Vue 3
import { watch } from 'vue';
...
...
watch(
() => yourNestedObject, // first param, your object
(currValue, prevValue) => { // second param, watcher callback
console.log(currValue, prevValue);
},
{ deep: true } // third param, for deep checking
);
You can refer to the documentation here: https://vuejs.org/guide/essentials/watchers.html
Here's a way to write watchers for nested properties:
new Vue({
...allYourOtherStuff,
watch: {
['foo.bar'](newValue, oldValue) {
// Do stuff here
}
}
});
You can even use this syntax for asynchronous watchers:
new Vue({
...allYourOtherStuff,
watch: {
async ['foo.bar'](newValue, oldValue) {
// Do stuff here
}
}
});
Now you can use this for vue 3 watch composition api:
watch(
() => item.someOtherProp,
someOtherProp => {
// Do some thing
}
);
Or use watch to compute value:
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
For Vue3 Options API
https://vuejs.org/guide/essentials/watchers.html#deep-watchers
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// Note: `newValue` will be equal to `oldValue` here
// on nested mutations as long as the object itself
// hasn't been replaced.
},
deep: true
}
}
}
本文标签: javascriptVuejsHow to properly watch for nested dataStack Overflow
版权声明:本文标题:javascript - Vue.js - How to properly watch for nested data - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736674297a1947096.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论