Subscribe on changes!

Breaking change in Vue.js reactivity - Version 3.2.26 compared to 2.6.14

avatar
Jan 12th 2022

Version

3.2.26

Reproduction link

codepen.io

Steps to reproduce

From my understanding of https://vuejs.org/v2/guide/reactivity.html#For-Objects in this situation there should be no reactivity on "y". While Vue.js 2.6.14 behaves as documented, in version 3.2.26 the changes of the added property "y" should not be visible.

What is expected?

y is not reactive, because it is not present in the data object

What is actually happening?

y is reactive, although it is not present in the data object


If this is not a bug, but by design, it should be documented.

Vue.js 2.x version that works as expected:

<html>
<head>
    <meta charset="utf-8">
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

</head>

<!-- Vue.js Template -->
<div id="app">
    <div>This should change: x={{x}}
    <div>This should not change: y={{this.$options.y }}</div>
    <button @click="increment">+1</button> 
    </div>
</div>

<!-- Vue.js Options API -->
<script>
    var app = new Vue( {
        el: '#app',
        data() { return {
          x: 0,
        } },
       
        methods: {
            increment: function () {
                this.x++; 
                app.y = this.x; 
            }
        }

    });

</script>
</html>
avatar
Jan 12th 2022

y isn't reactive. But since x is reactive, and the component is being re-rendered for that reason, the re-render prints the new value for y. You can verify that by commenting out the div that depends on x.

This would have behaved absolutely the same in Vue 2, I don't see what you refer to as a breaking change.

avatar
Jan 12th 2022

Please have a look at https://codepen.io/hschwichtenberg/pen/jOGQvZK This is the same code in Vue 2. But the y value is not printed.

avatar
Jan 12th 2022

Yeah but that's because you add it to the component instance returned from new Vue(), then attempt to read it from instance.$options - where you did not add it.

Your two examples simply don't do the same thing.

If your Vue 2 example actually did add y to the options object, like you do in the Vue 3 example, it would work just the same.

Actually, in Vue 2, the options object will not be used as-is, so adding to it will not be reflected in the template if you read from $options in there.

However, if you just do this:

- app.y = this.x
+ this.$options.y = this.x

you will find that

  1. y is being printed into the template.
  2. it's not reactive.

This it not related to reactivity, it's related to how new Vue() treats the options object (uses a copy of it for $options) that you pass to it vs. how createApp does (uses the options object as it is), which to me is rather an implementation detail.

avatar
Jan 13th 2022

Thank you very much @LinusBorg for this explanation, especially the details about the different behaviour of new Vue() vs. createApp.

Next, I would have addressed why y: 0 in the options object doesn't work either :-). It's great that you recognized and addressed this upfront!

But I have the feeling that the difference between new Vue () and createApp () could also be an surprising issue for other developers and a documentation in https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes would be helpful.

Also, this seems to a notable difference between Options API and Composition API, because in following sample I don't get the updated y printed, because it is not a RefImp.

<template>
 <div id="app">
    <div>x should be reactive: x={{x}}
    <div>y should NOT be reactive: y={{y }}</div>
    <button @click="increment">Please click here</button> 
    </div>
</div>
</template>

<script setup lang="ts">
import {  ref } from 'vue';

let x = ref(0);
let y = 0; // no RefImpl

function increment()
{
    x.value++; 
    y++; 
}
</script>
avatar
Jan 13th 2022

But I have the feeling that the difference between new Vue () and createApp () could also be an surprising issue for other developers and a documentation in v3.vuejs.org/guide/migration/introduction.html#breaking-changes would be helpful.

We would welcome a proposal in the docs repo.

Personally I would consider this an implementation detail that is not a real breaking change. Wether or not $options, but it might. be helpful to someone in some rare edge case.

Also, this seems to a notable difference between Options API and Composition API, because in following sample I don't get the updated y printed, because it is not a RefImp.

Your example works fine for me

avatar
Jan 13th 2022

Also, this seems to a notable difference between Options API and Composition API, because in following sample I don't get the updated y printed, because it is not a RefImp.

Your example works fine for me

Oh... I see, that the y values are updated in the Vue SFC Playground. But y is not updated in my local Vue CLI project. Exactly the same code. How can that be???

2022-01-13_08-49-56

avatar
Jan 13th 2022

I don't know, outdated dependencies maybe? Hard to say without looking at the thing myself.

Anyway, if you want to debug this, please take it to chat.vuejs.org - I'm there alongside many other capable people.

This issue is resovled.

avatar
Jan 13th 2022

Of course I tried in a fresh project based on the latest CLI that npm gave me: @vue/cli 4.5.15

I will try at chat.vuejs.org, altough I have the impression, this might be an issue.

Thanks again for your help!

avatar
Jan 13th 2022

Just in case, you are interested: Here is the Vue CLI project with the same code as in SFC Playground

github_repo.zip