Breaking change in Vue.js reactivity - Version 3.2.26 compared to 2.6.14
Version
3.2.26
Reproduction link
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>
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.
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.
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
y
is being printed into the template.- 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.
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>
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.
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???
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.
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!
Just in case, you are interested: Here is the Vue CLI project with the same code as in SFC Playground