I want to know why I changed the specific item of an array and the page doesn't update. I know doc of vue.js points out that:
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
.
It says we should do that, but I don't know why. And I've found a similar question(Vue puted issue - when will it pute again) about that.
Here is some code from the question above:
// works well
data() {
return {
cart: {
item: {
nums: 10,
price: 10,
},
},
}
},
puted: {
total() {
return this.cart.item.nums * this.cart.item.price
},
},
methods: {
set() {
//why it worked.
this.cart.item = {
nums: 5,
price: 5,
}
},
},
// oops! not working!
data() {
return {
cart: [
{
nums: 10,
price: 10,
},
],
}
},
puted: {
total() {
return this.cart[0].nums * this.cart[0].price
},
},
methods: {
set() {
this.cart[0] = {
nums: 5,
price: 5,
}
},
},
I'm confused about the answer from the question:
total will be recalculated if
this.cart
is marked as changed,this.cart[0]
is marked as changed or ifthis.cart[0].nums
orthis.cart[0].price
is changed. The problem is that you are replacing the object inthis.cart[0]
. This means thatthis.cart[0].price
and nums do not change, because those still point to the old object.
If I have replaced the object in this.cart[0]
, why this.cart[0]
isn't marked as changed? why this.cart[0].price
and nums
still point to old object? I have changed the this.cart[0]! right?
And why in the first situation it works well? also replace the object . What's the difference between the two scenarios?
I want to know why I changed the specific item of an array and the page doesn't update. I know doc of vue.js points out that:
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
.
It says we should do that, but I don't know why. And I've found a similar question(Vue puted issue - when will it pute again) about that.
Here is some code from the question above:
// works well
data() {
return {
cart: {
item: {
nums: 10,
price: 10,
},
},
}
},
puted: {
total() {
return this.cart.item.nums * this.cart.item.price
},
},
methods: {
set() {
//why it worked.
this.cart.item = {
nums: 5,
price: 5,
}
},
},
// oops! not working!
data() {
return {
cart: [
{
nums: 10,
price: 10,
},
],
}
},
puted: {
total() {
return this.cart[0].nums * this.cart[0].price
},
},
methods: {
set() {
this.cart[0] = {
nums: 5,
price: 5,
}
},
},
I'm confused about the answer from the question:
total will be recalculated if
this.cart
is marked as changed,this.cart[0]
is marked as changed or ifthis.cart[0].nums
orthis.cart[0].price
is changed. The problem is that you are replacing the object inthis.cart[0]
. This means thatthis.cart[0].price
and nums do not change, because those still point to the old object.
If I have replaced the object in this.cart[0]
, why this.cart[0]
isn't marked as changed? why this.cart[0].price
and nums
still point to old object? I have changed the this.cart[0]! right?
And why in the first situation it works well? also replace the object . What's the difference between the two scenarios?
Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Aug 22, 2019 at 18:24 ArchsxArchsx 9723 gold badges13 silver badges22 bronze badges3 Answers
Reset to default 12Vue is pretty explicit about this. It's a JavaScript limitation. JavaScript does not support the ability to detect when an array element changes, only when arrays change size as a result of adding or removing elements. Therefore replacing an element is not detectable.
What Vue does behind the scenes is sets up mechanics to watch for when objects change, which is something JavaScript does support. So array elements that are objects are things Vue can detect changes for. Since Vue cannot detect an array element being replaced, it doesn't know to stop looking at the old object and start looking at the new one.
The answer is also well-documented: if you want to update an item in an array, then use Vue.set()
. This allows Vue to know that the array element is changing, so it knows to stop looking at the old object and start looking at the new one.
The solution therefore looks something like this:
Vue.set(this.cart, 0, {nums: 5, price: 5});
To make it short: Arrays are not registered reactive.
To assign a value use one of those equivalents:
Vue.set(array, index, value)
vm.$set(array, index, value)
array.splice(index, 1, value)
In the Observer
class constructor(value)
it is distinguished between Array and not Array:
if (Array.isArray(value)) {
// steps through all indices
this.observeArray(value)
} else {
// steps through all keys
this.walk(value)
}
But what is this.observeArray(value: Array)
?
For Arrays it actually just calls new Observer(value[index])
- the constructor(value[index])
There is no getter
nor setter
placed to watch for changes in this iteration over an Array
To inspect the second path: this.walk(obj: Object)
:
It calls defineReactive(obj, keys[i])
wich will in a simplified version will do:
defineReactive(obj, key=keys[i]){
let val = obj[key]
Object.defineProperty(obj, key, {
get(){
// registers in Deb
dep.depend()
// if array also
return val
},
set(newVal){
val = newVal
// adds to subscriber
deb.notify()
}
})
}
But array proptotypes are patched to use the Observer this.__ob__.deb
to notify on usage.
An alternative is the usage of set registered as:
Vue.prototype.$set
Vue.set
For Arrays Vue.set(array, index, value)
calls array.splice(index, 1, value)
and this again is the same as ob.observeArray(value)
and ob.deb.notify()
Another solution for your set()
method instead of use Vue.set()
can be:
set() {
this.cart.splice(0, 1, {
nums: 5,
price: 5,
});
},