Regression: async updates from outside of component are not being watched
Version
3.0.0
Reproduction link
https://jsfiddle.net/0bjtu7oe/
Steps to reproduce
HTML:
<div id="app"></div>
JS
class Counter {
constructor() {
this.value = 0;
setInterval(() => {
this.value ;
}, 1000);
}
}
Vue.createApp({
data() {
return {
counter: null
};
},
methods: {
start() {
this.counter = new Counter();
}
},
template: `
<div>
<div v-if="!counter" @click="start"> Click to start </div>
<div v-if="counter"> Counter: {{ counter.value }} </div>
</div>
`
}).mount('#app');
Click "Click to start"
What is expected?
Counter is being updated every second
What is actually happening?
Counter is not being updated
In vue 2 this works: https://jsfiddle.net/9e6L7vg3/
I think this may be connected to starting acync operation in constructor. As a workaround, this works correctly:
class Counter {
constructor() {
this.value = 0;
}
start() {
setInterval(() => {
this.value++;
}, 1000);
}
}
Vue.createApp({
data() {
return {
counter: null
};
},
methods: {
start() {
this.counter = new Counter();
this.counter.start();
}
},
template: `
<div>
<div v-if="!counter" @click="start"> Click to start </div>
<div v-if="counter"> Counter: {{ counter.value }} </div>
</div>
`
}).mount('#app');
You should declare your reactive values as such, e.g.:
this.value = Vue.ref(0);
setInterval(() => {
this.value.value++;
}, 1000);
This would be another workaround for this issue.
But what if Counter
is third party class? Or I want Counter
to be decoupled from vue library?
Vue supports such objects, by automatically wrapping object properties in proxy. And it works in most cases. I just found this one corner case where it used to work in vue 2 and now it doesn't work in vue 3 (hence logged as "regression").
Is this behaviour expected?
Is this behaviour expected?
Yes. In the original example, you essentially bind the setTimeout's callback to the original instance's this
, before the proxy exists. That means the mutation done by the callback is done on the original object, not the proxy.
In the workaround, you do it in a method called through the proxy, so in this case, this
references the proxy, and changes done in the setTimeout's callback can be tracked.
We always recommended to use plain objects, never stateful classes that manage side-effects etc, in reactive data
even back in Vue 2. We already had (other) edge cases that broke with stateful classes in Vue 2, now you found one such edge case for the new Proxy based reactivity.
We can have a discussion in the docs-next
repo about how to document these edge cases better. But since it's not a bug, this issue is closed.