Reputation: 55664
In the snippet below I have two Vue components, each with an items
array containing two objects. Each component's template is set up to show an object of the items
array if that object's show
property is truthy. The first object in the array initially has a show
value of true
. The second object does not have a show
value set.
new Vue({
el: '#ex1',
data: () => ({ items: [{name: 'apple', show: true}, {name: 'banana'}] }),
mounted() {
// 2nd item's show property setting is not detected here
this.items[1].show = true;
}
})
new Vue({
el: '#ex2',
data: () => ({ items: [{name: 'apple', show: true}, {name: 'banana'}] }),
mounted() {
// 2nd item's show property setting IS detected...
this.items[1].show = true;
// ...when the 1st item's show property is set to false
this.items[0].show = false;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="ex1">
Example 1:
<div v-if="items[0].show">{{ items[0] }}</div>
<div v-if="items[1].show">{{ items[1] }}</div>
</div>
<div id="ex2">
Example 2:
<div v-if="items[0].show">{{ items[0] }}</div>
<div v-if="items[1].show">{{ items[1] }}</div>
</div>
In the first example, I set the show
property of the second object in the items
array to true
. Vue does not detect the change in the template, which is expected.
In the second example, I set the show
property of the second object to true
, and then set the show
property of the first object to be false
. In this case, Vue does detect the change in both elements of the items
array and updates the template accordingly. I would not expect this to be the case, given Vue's Change Detection Caveats.
This seems like a pretty innocuous bug (if it even qualifies as a bug), so I'm not sure if it's worth reporting. But, I was wondering if anyone would be able to explain this behavior. Why does Vue detect the addition of the show
property to the object in the second example?
Upvotes: 3
Views: 203
Reputation: 82489
Changing the reactive property (show
of the first object) triggers a re-render. Vue faithfully renders the current state of the objects referenced by the template, which includes the second object.
The second object's show property is still not reactive, and changes to it will not update the Vue. It's simply the change to the state of the reactive property on the first object that triggers the update.
In other words, changing the show property of the second object, because it was added after the fact, will never update the Vue, but changing the show property on the first object will always update, and the update will just render the current state of the array no matter how it was added.
I wouldn't consider this a bug.
One easy way to kind of see the difference is to log the array to the console after the update. You will see the show property of the second object is not observed, but is nevertheless there.
Edit I wanted to clarify slightly based on a comment. The reason the changes to the second object appear in the Vue is because the entire Vue is re-rendered and the second object is referenced directly in the Vue. If you, for example, passed the value down to a component, the second object would not be re-rendered.
Here is an example showing that the second object is never updated when the value is passed to a component (because the component manages the rendering).
console.clear()
Vue.component("item", {
props: ["item"],
template: `
<div>Item Show: {{item.show}}</div>
`
})
new Vue({
el: '#ex2',
data: () => ({ items: [{name: 'apple', show: true}, {name: 'banana'}] }),
mounted() {
// 2nd item's show property setting IS detected...
this.items[1].show = true;
// ...when the 1st item's show property is set to false
this.items[0].show = false;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="ex2">
This will show false, because that is the current state and this object's show
property is reactive
<item :item="items[0]"></item>
<hr>
This will not show *anything*, despite the current state of show=true
because the object's show property is not reactive
<item :item="items[1]"></item>
</div>
Upvotes: 5
Reputation: 442
as described in the link you post:
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the
Vue.set(object, key, value)
to solve instead of this.items[1].show = true you can write:
Vue.set(this.items[1], 'show', true)
hope it's clear
Upvotes: -1