【问题标题】:why vue change specific array member not update dom为什么 vue 更改特定数组成员不更新 dom
【发布时间】:2019-08-22 18:24:35
【问题描述】:

我想知道为什么我更改了数组的特定项而页面没有更新。我知道 vue.js 的文档指出:

由于 JavaScript 的限制,Vue 无法检测到数组的以下更改:

当您直接使用索引设置项目时,例如vm.items[indexOfItem] = newValue.

它说我们应该这样做,但我不知道为什么。我发现了一个类似的问题(Vue computed issue - when will it compute again)。

这是上面问题中的一些代码:

// works well

data() {
  return {
    cart: {
      item: {
        nums: 10,
        price: 10,
      },
    },
  }
},
computed: {
  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,
      },
    ],
  }
},
computed: {
  total() {
    return this.cart[0].nums * this.cart[0].price
  },
},
methods: {
  set() {
    this.cart[0] = {
      nums: 5,
      price: 5,
    }
  },
},

我对问题的答案感到困惑:

如果this.cart 被标记为已更改,this.cart[0] 被标记为已更改或this.cart[0].nums 或this.cart[0].price 已更改,则将重新计算总数。问题是您正在替换this.cart[0] 中的对象。这意味着 this.cart[0].price 和 nums 不会改变,因为它们仍然指向旧对象。

如果我替换了this.cart[0] 中的对象,为什么this.cart[0] 没有标记为已更改?为什么this.cart[0].price 和nums 仍然指向旧对象? 我已更改 this.cart[0]!对吗?

为什么在第一种情况下效果很好? 也替换对象。这两种情况有什么区别?

【问题讨论】:

    标签: javascript vue.js


    【解决方案1】:

    Vue 对此非常明确。这是一个 JavaScript 限制。 JavaScript 不支持检测数组元素何时更改的能力,仅当数组因添加或删除元素而改变大小时才支持。因此无法检测到替换元素。

    Vue 在幕后所做的是设置机制以观察对象何时发生变化,这是 JavaScript 支持的。所以作为对象的数组元素是 Vue 可以检测到变化的东西。由于 Vue 无法检测到数组元素被替换,它不知道停止查看旧对象并开始查看新对象。

    答案也有据可查:如果要更新数组中的项目,请使用Vue.set()。这让 Vue 知道数组元素正在发生变化,因此它知道停止查看旧对象并开始查看新对象。

    因此,解决方案如下所示:

    Vue.set(this.cart, 0, {nums: 5, price: 5});
    

    【讨论】:

    • 总结.Vue 检测对象变化。但不是数组成员被替换。如果代码是 this.cart[0].num = 5(如原始问题中所述),它将更新 dom。对吗?
    • @Archsx 正确。 Vue 应该能够轻松检测到这种变化。
    【解决方案2】:

    简而言之:数组未注册为反应式。

    要分配一个值,请使用这些等价物之一:

    • Vue.set(array, index, value)
    • vm.$set(array, index, value)
    • array.splice(index, 1, value)

    在Observer类constructor(value)中区分Array和非Array:

    if (Array.isArray(value)) {
      // steps through all indices
      this.observeArray(value)
    } else {
      // steps through all keys
      this.walk(value)
    }
    

    但是this.observeArray(value: Array) 是什么?

    对于数组,它实际上只是调用 new Observer(value[index]) - constructor(value[index])

    没有 getter 也 setter 用于监视此迭代中对数组的更改


    检查第二条路径:this.walk(obj: Object):

    它会调用defineReactive(obj, keys[i]),在简化版本中会这样做:

    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()
        }
      })
    }
    

    但数组原型被修补以使用观察者this.__ob__.deb to notify 使用。


    另一种方法是使用set 注册为:

    对于数组Vue.set(array, index, value) 调用array.splice(index, 1, value) 这又与ob.observeArray(value) 和ob.deb.notify() 相同

    【讨论】:

      【解决方案3】:

      您的set() 方法而不是使用Vue.set() 的另一种解决方案可以是:

      set() {
          this.cart.splice(0, 1, {
            nums: 5,
            price: 5,
          });
        },
      

      【讨论】:

        猜你喜欢
        • 2021-04-01
        • 1970-01-01
        • 2019-01-30
        • 2015-03-15
        • 2019-08-04
        • 1970-01-01
        • 2020-04-30
        • 1970-01-01
        • 2023-02-02
        相关资源
        最近更新 更多