Vue - 如果添加或修改了对象,则深度观察对象数组的变化

Posted

技术标签:

【中文标题】Vue - 如果添加或修改了对象,则深度观察对象数组的变化【英文标题】:Vue - Deep watch change of array of objects, either if a object is added of modified 【发布时间】:2019-02-18 05:08:21 【问题描述】:

我正在尝试在 vue.js 中创建一个元素,这样当我更新我的购物车时,它会显示一个警告,其中包含已添加/更新到购物车的项目。因此,如果我添加一辆新车,它将显示最后添加的汽车。

cars: [
   name: 'Porsche', quantity: 2,
   name: 'Ferrari', quantity: 1,
   name: 'Toyota', quantity: 3
]

cars: [
   name: 'Porsche', quantity: 2,
   name: 'Ferrari', quantity: 1,
   name: 'Toyota', quantity: 3,
   name: 'Mustang', quantity: 1
]

会显示

<div>
You have 1 x Mustang in Cart
</div>

但是,如果我更新已经在购物车中的汽车的数量,它将显示最后一辆车已更新。

cars: [
   name: 'Porsche', quantity: 2,
   name: 'Ferrari', quantity: 1,
   name: 'Toyota', quantity: 3
]

cars: [
   name: 'Porsche', quantity: 2,
   name: 'Ferrari', quantity: 1,
   name: 'Toyota', quantity: 4
]

会显示

<div>
You have 4 x Toyota in Cart
</div>

到目前为止,我已经在 this answer 上运行它

new Vue(
el: '#app',
data: 
    cars: [
       name: 'Porsche', quantity: 2,
       name: 'Ferrari', quantity: 1,
       name: 'Toyota', quantity: 3
    ]

);

Vue.component('car-component', 
props: ["car"],
data: function() 
  return 
    lastAdded:''
  
,
template: `
  <div>
    You have lastAdded.quantity x lastAdded.name in Cart
  </div>`,

watch: 
  car: 
    handler: function(newValue) 
       this.lastAdded = newValue;
    ,
      deep: true
  

);

html

<script src="https://unpkg.com/vue@2.5.17/dist/vue.js"></script>
<body>
  <div id="app">
    <p>Added to Cart:</p>
    <car-component :car="car" v-for="car in cars"></car-component>
  </div>
</body>

关键是现在它只检测一个物体何时已经在购物车中并改变数量,而不是当有一辆新车添加时。我试图和另一个观察者一起玩,但是没有用。提前致谢!

【问题讨论】:

这不会显示每辆车的警告信息吗?我以为您只希望它显示在购物车中添加/更新的最后一项。 Vue 在跟踪对象和数组时需要注意一些注意事项。看看这个:vuejs.org/v2/guide/list.html#Caveats @DecadeMoon 我编辑了 html,它应该显示 lastItem 值 @Joe82 我的意思是,你对每辆车重复&lt;car-component&gt;,而&lt;car-component&gt; 将呈现“你有......在购物车中”,所以你会有多条消息.我很困惑,因为您的问题表明您只想为最后一项显示一条消息,而不是为每项显示一条消息。 @Joe82 否则,如果你的代码是正确的,你就不需要深入观察,也不需要做你想做的事情。 【参考方案1】:

嗯,我该怎么做?

在我看来,我们有一个对象数组,我们正在跟踪最近添加或修改的对象。当然。

所以,我想我只想跟踪最近修改的对象并渲染它。

首先是html:

<div id="app">
    <p>Added to Cart:</p>
    <car-component :car="latestCar"></car-component>
</div>

和 vue 实例:

new Vue(
el: '#app',
data: 
    cars: [
       name: 'Porsche', quantity: 2,
       name: 'Ferrari', quantity: 1,
       name: 'Toyota', quantity: 3
    ],
    latestCar: 
,
methods: 
  updateLatestCar(car) 
     this.latestCar = car;
     //call this method from any other method where updates take place
     //so if would be called from your addCar method and your updateCar method
     //(which I assume exist even though they are not shown in your code)        
  

);

Vue.component('car-component', 
props: ["car"],
data: function() 
  return 
    lastAdded:''
  
,
template: `
  <div>
    You have lastAdded.quantity x lastAdded.name in Cart
  </div>`,

watch: 
  car: 
    handler: function(newValue) 
       this.lastAdded = newValue;
    ,
      deep: true
  

);

如果您通过 Vue 实例外部的某种方法修改对象数组,则需要额外考虑。

但看起来你在 Vue 实例方法块中有一些方法,如下所示:

addCar(car) 
  this.cars.push(car);
  this.updateLatestCar(car);
,
updateCar(index, car) 
  this.cars[index] = car;
  this.updateLatestCar(car);

【讨论】:

感谢您的回答!我想过这样的事情,但我宁愿和观察者一起解决它。关键是,我有几种方法可以更新存储在 vuex 中的对象数组,并且必须为每个对象添加此更新方法似乎不是最佳解决方案。【参考方案2】:

您可以将整个cars[] 数组传递给&lt;car-component&gt;,并允许组件确定cars[] 的哪个元素显示关于以下内容的消息:

    car-component 中,添加一个prop(typed 以确保安全)来保存传入的cars[]

    Vue.component('car-component', 
      // ...
      props: 
        cars: Array
      ,
    
    

    添加两个数据属性:

    car - 当前的汽车。

    copyOfCars - cars[] 的最后一个已知副本,用于确定哪个数组元素已更改。 注意:虽然为观察者提供了被观察属性的旧值和新值,但旧值实际上并不表示对象数组的先前值。

    Vue.component('car-component', 
      //...
      data() 
        return 
          car: ,
          copyOfCars: undefined, // `undefined` because we don't need it to be reactive
        ;
      ,
    
    

    定义一个方法(例如,命名为findActiveCar)来确定给定cars[] 中的哪个元素是最近“活动的”(新添加或修改的)。

    Vue.component('car-component', 
      // ...
      methods: 
        /**
         * Gets the newest/modified car from the given cars
         */
        findActiveCar(newCars) 
          if (!newCars || newCars.length === 0) return ;
    
          let oldCars = this.copyOfCars;
    
          // Assume the last item of `newCars` is the most recently active
          let car = newCars[newCars.length - 1];
    
          // Search `newCars` for a car that doesn't match its last copy in `oldCars`
          if (oldCars) 
            for (let i = 0; i < Math.min(newCars.length, oldCars.length); i++) 
              if (newCars[i].name !== oldCars[i].name
                  || newCars[i].quantity !== oldCars[i].quantity) 
                car = newCars[i];
                break;
              
            
          
    
          this.copyOfCars = JSON.parse(JSON.stringify(newCars));
          return car;
        
      
    
    

    cars 属性上定义一个watcher,将car 设置为来自findActiveCar() 的新/修改项。

    Vue.component('car-component', 
      // ...
      watch: 
        cars: 
          handler(newCars) 
            this.car = this.findActiveCar(newCars);
          ,
          deep: true, // watch subproperties of array elements
          immediate: true,  // run watcher immediately on `this.cars[]`
        
      ,
    
    

Vue.component('car-component', 
  props: 
    cars: Array,
  ,
  data() 
    return 
      car: ,
      copyOfCars: undefined,
    
  ,
  template: `<div>You have car.quantity x car.name in Cart</div>`,

  watch: 
    cars: 
      handler(newCars) 
        this.car = this.findActiveCar(newCars);
      ,
      deep: true,
      immediate: true,
    
  ,

  methods: 
    findActiveCar(newCars) 
      if (!newCars || newCars.length === 0) return ;

      let oldCars = this.copyOfCars;
      let car = newCars[newCars.length - 1];

      if (oldCars) 
        for (let i = 0; i < Math.min(newCars.length, oldCars.length); i++) 
          if (newCars[i].name !== oldCars[i].name
              || newCars[i].quantity !== oldCars[i].quantity) 
            car = newCars[i];
            break;
          
        
      

      this.copyOfCars = JSON.parse(JSON.stringify(newCars));
      return car;
    
  
);


new Vue(
  el: '#app',
  data: () => (
    cars: [
       name: 'Porsche', quantity: 2,
       name: 'Ferrari', quantity: 1,
       name: 'Toyota', quantity: 3
    ]
  ),
  methods: 
    addCar() 
      this.cars.push(
        name: 'Mustang', quantity: 1
      )
    
  
)
<script src="https://unpkg.com/vue@2.5.17"></script>

<div id="app">
  <h1>Added to Cart</h1>
  <button @click="addCar">Add car</button>
  <ul>
    <li v-for="(car, index) in cars" :key="car.name + index">
      <span>car.name (car.quantity)</span>
      <button @click="car.quantity++">+</button>
    </li>
  </ul>
  <car-component :cars="cars" />
</div>

【讨论】:

效果很好!只有一个问题:props: cars: Array 部分是做什么的,与props:['cars'] 有什么不同?谢谢! Vue 允许您specify the expected type of a prop。因此,props: cars: Array 告诉 Vue 如果提供的 cars 实际上不是一个数组,则发出错误。另一方面,props: ['cars'] 没有类型检查,cars 可以是任何东西。

以上是关于Vue - 如果添加或修改了对象,则深度观察对象数组的变化的主要内容,如果未能解决你的问题,请参考以下文章

Vue子组件(deep)深度监听props对象属性无效的解决办法

vue的watcher 关于数组和对象

vue深度响应Array中的Object(vue深度响应数组内的对象)

Vue对象数据收集依赖具体流程

Vue:深层嵌套对象上的观察者会记录 oldVal 和 newVal 吗?

vue中的观察者模式和发布订阅者模式