Subscribe on changes!

Regression: async updates from outside of component are not being watched

avatar
Oct 11th 2020

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/

avatar
Oct 11th 2020

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');

https://jsfiddle.net/zoutcwhk/

avatar
Oct 11th 2020

You should declare your reactive values as such, e.g.:

        this.value = Vue.ref(0);
        setInterval(() => {
            this.value.value++;
        }, 1000);
avatar
Oct 11th 2020

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?

avatar
Oct 11th 2020

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.