Vue.js: cómo ver correctamente los datos anidados

414

Estoy tratando de entender cómo observar adecuadamente alguna variación de utilería. Tengo un componente primario (archivos .vue) que recibe datos de una llamada ajax, coloca los datos dentro de un objeto y lo usa para representar algún componente secundario a través de una directiva v-for, debajo de una simplificación de mi implementación:

<template>
    <div>
        <player v-for="(item, key, index) in players"
            :item="item"
            :index="index"
            :key="key"">
        </player>
    </div>
</template>

... luego dentro de la <script>etiqueta:

 data(){
     return {
         players: {}
 },
 created(){
        let self = this;
        this.$http.get('../serv/config/player.php').then((response) => {
            let pls = response.body;
            for (let p in pls) {
                self.$set(self.players, p, pls[p]);
            }
    });
}

Los objetos objeto son así:

item:{
   prop: value,
   someOtherProp: {
       nestedProp: nestedValue,
       myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
    },
}

Ahora, dentro del componente "jugador" de mi hijo, estoy tratando de ver la variación de propiedad de cualquier artículo y uso:

...
watch:{
    'item.someOtherProp'(newVal){
        //to work with changes in "myArray"
    },
    'item.prop'(newVal){
        //to work with changes in prop
    }
}

Funciona pero me parece un poco complicado y me preguntaba si esta es la forma correcta de hacerlo. Mi objetivo es realizar alguna acción cada vez que propcambia u myArrayobtiene nuevos elementos o alguna variación dentro de los existentes. Cualquier sugerencia será apreciada.

El plastico
fuente
99
Simplemente use "item.someOtherProp": function (newVal, oldVal){como @Ron sugirió.
Reiner
1
@Reiner esto era exactamente lo que estaba tratando de evitar en la pregunta; es lo mismo con la sintaxis de ES5. En realidad, no hay ninguna sobrecarga adicional al mirar una computadora y es una técnica útil en diversas situaciones.
craig_h
1
¿Es posible observar un cambio en todo el keysinterior de un objeto? Ejemplo:"item.*": function(val){...}
Charlie,

Respuestas:

623

Puede usar un observador profundo para eso:

watch: {
  item: {
     handler(val){
       // do stuff
     },
     deep: true
  }
}

Esto detectará ahora cualquier cambio en los objetos de la itemmatriz y adiciones a la matriz misma (cuando se usa con Vue.set ). Aquí hay un JSFiddle: http://jsfiddle.net/je2rw3rs/

EDITAR

Si no desea ver cada cambio en el objeto de nivel superior, y solo desea una sintaxis menos incómoda para ver objetos anidados directamente, simplemente puede ver un computeden su lugar:

var vm = new Vue({
  el: '#app',
  computed: {
    foo() {
      return this.item.foo;
    }
  },
  watch: {
    foo() {
      console.log('Foo Changed!');
    }
  },
  data: {
    item: {
      foo: 'foo'
    }
  }
})

Aquí está el JSFiddle: http://jsfiddle.net/oa07r5fw/

craig_h
fuente
2
de esta manera, se invocará "handler" cada vez que cualquier accesorio tenga una variación, mi propósito es separar los controladores para observar si los cambios ocurren en "prop" o dentro de myArray en "someOtherProp" por separado
Plástico
99
Si solo desea observar los cambios en objetos anidados específicos, entonces lo que está haciendo está bien. Si desea una sintaxis menos incómoda, puede ver una computeden su lugar: jsfiddle.net/c52nda7x
craig_h
Exactamente lo que estaba buscando, ahora me parece muy lógico: crear una propiedad calculada que devuelva el accesorio anidado y esté atento. Muchas gracias.
Plástico
Solo quiero agregar: que la segunda opción realmente no es muy diferente del observador original. Internamente, una propiedad calculada es solo un observador en todos los objetos / valores referenciados en la función que establece un valor de datos al resultado de la función. - Entonces, probablemente solo haga que el código sea un poco más complicado con el mismo efecto.
Falco
22
@Falco Acabo de descubrir la respuesta en un comentario aquí . peerbolte indicó que se puede hacer poniendo el nombre del reloj entre comillas simples. He probado esto y funciona. Entonces, en este caso, sería watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} bastante hábil. ¡Amo a Vue!
Ron C
437

Otro buen enfoque y uno que es un poco más elegante es el siguiente:

 watch:{
     'item.someOtherProp': function (newVal, oldVal){
         //to work with changes in someOtherProp
     },
     'item.prop': function(newVal, oldVal){
         //to work with changes in prop
     }
 }

(Aprendí este enfoque de @peerbolte en el comentario aquí )

Ron C
fuente
47
mucho más elegante (no solo un poco): P. Esto debería estar en la documentación de Vue.js ya que es un caso de uso común. Llevo horas buscando una solución, gracias por su respuesta.
Claudiu
11
@symba Es interesante que la gente no haya notado que esto es lo mismo que el OP pidió evitar, solo en ES5sintaxis. Por supuesto, podríamos argumentar que el ES2015 method definitionsí mismo no es muy elegante, pero de alguna manera pierde el punto de la pregunta, que es cómo evitar envolver el nombre con comillas. Supongo que la mayoría de la gente está pasando por alto la pregunta original, que ya contiene la respuesta, y en realidad se está buscando algo que, como usted dice, no está muy bien documentada,
craig_h
1
@craig_h punto super interesante. Originalmente estaba cazando durante horas para encontrar una buena manera de ver un accesorio anidado. Y el título de esta pregunta fue la pregunta más cercana que encontré, pero eché de menos que enumerara un accesorio citado en el cuerpo de la pregunta, pero que las respuestas no fueran tan elegantes. Finalmente encontré el enfoque de apoyo citado en el comentario de otra pregunta y estuve tentado de crear una pregunta solo para responderla como documentación del enfoque de apoyo citado en SO. Pero esta pregunta era exactamente cuál habría sido mi título, así que agregué la respuesta aquí. Quizás debería haber creado una nueva pregunta.
Ron C
99
@RonC Esta es una pregunta popular y su respuesta es exactamente lo que mucha gente está buscando, por lo que creo que aquí está bien. Todos tenemos solo un tiempo limitado, y la mayoría de nosotros apenas leemos las preguntas, por lo que ciertamente no fue una crítica de la respuesta que proporcionó, solo estoy siendo un poco pedante al señalar que mi respuesta original era específica para el pregunta y no estaba destinado a ser obstinado. En realidad, me gusta el enfoque de "mirar computarizado", sin embargo, muchos prefieren el enfoque de "citas" menos enrevesado, que resumió sucintamente en su respuesta.
craig_h
10
La documentación oficial incluye este enfoque ahora
feihcsim
20

Vigilancia profunda de VueJs en objetos secundarios

new Vue({
    el: "#myElement",
    data: {
        entity: {
            properties: []
        }
    },
    watch: {
        'entity.properties': {
            handler: function (after, before) {
                // Changes detected. Do work...     
            },
            deep: true
        }
    }
});
Alper Ebicoglu
fuente
8

¿Cómo si quieres ver una propiedad por un tiempo y luego dejar de verla?

¿O para mirar una propiedad de componente hijo de biblioteca?

Puede usar el "observador dinámico":

this.$watch(
 'object.property', //what you want to watch
 (newVal, oldVal) => {
    //execute your code here
 }
)

El $watchdevuelve una función de desbloqueo que dejará de ver si se llama.

var unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()

También puedes usar la deepopción:

this.$watch(
'someObject', () => {
    //execute your code here
},
{ deep: true }
)

Asegúrate de consultar los documentos

roli roli
fuente
2
Según los documentos , no debe usar las funciones de flecha para definir un observador:Note that you should not use an arrow function to define a watcher (e.g. searchQuery: newValue => this.updateAutocomplete(newValue)). The reason is arrow functions bind the parent context, so this will not be the Vue instance as you expect and this.updateAutocomplete will be undefined.
wonder95
7

No lo veo mencionado aquí, pero también es posible usar el vue-property-decoratorpatrón si está ampliando su Vueclase.

import { Watch, Vue } from 'vue-property-decorator';

export default class SomeClass extends Vue {
   ...

   @Watch('item.someOtherProp')
   someOtherPropChange(newVal, oldVal) {
      // do something
   }

   ...
}
getglad
fuente
5

Otra forma de agregar que solía 'hackear' esta solución era hacer esto: configuré un computedvalor separado que simplemente devolvería el valor del objeto anidado.

data : function(){
    return {
        my_object : {
            my_deep_object : {
                my_value : "hello world";
            }.
        },
    };
},
computed : {
    helper_name : function(){
        return this.my_object.my_deep_object.my_value;
    },
},
watch : {
    helper_name : function(newVal, oldVal){
        // do this...
    }
}
Zac
fuente
bueno;) pero ¿qué
pasa
3

Mi problema con la respuesta aceptada de usar deep: truees que cuando observo una matriz en profundidad, no puedo identificar fácilmente qué elemento de la matriz contiene el cambio. La única solución clara que he encontrado es esta respuesta, que explica cómo hacer un componente para que pueda ver cada elemento de la matriz de forma individual.

krubo
fuente
1
Sí, eso es cierto, pero eso no es para lo que es un observador profundo (de hecho, encontrará oldValy newValambos hacen referencia al mismo objeto, por lo que son lo mismo). La intención de a deep watcheres hacer algo cuando los valores cambian, como emitir un evento, hacer una llamada ajax, etc. De lo contrario, probablemente desee un componente, como se señala en la respuesta de Mani.
craig_h
¡Tuve el mismo problema de rastrear los cambios individuales! Sin embargo, encontré una solución y la documenté aquí .
Erik Koopmans
3

Seguimiento de elementos modificados individuales en una lista

Si desea ver todos los elementos de una lista y saber qué elemento de la lista cambió, puede configurar observadores personalizados en cada elemento por separado, de esta manera:

var vm = new Vue({
  data: {
    list: [
      {name: 'obj1 to watch'},
      {name: 'obj2 to watch'},
    ],
  },
  methods: {
    handleChange (newVal, oldVal) {
      // Handle changes here!
      // NOTE: For mutated objects, newVal and oldVal will be identical.
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

Si su lista no se llena de inmediato (como en la pregunta original), puede mover la lógica createda donde sea necesario, por ejemplo, dentro del .then()bloque.

Viendo una lista cambiante

Si su propia lista se actualiza para tener elementos nuevos o eliminados, he desarrollado un patrón útil que "superficial" observa la lista en sí, y observa / desbloquea dinámicamente los elementos a medida que la lista cambia:

// NOTE: This example uses Lodash (_.differenceBy and _.pull) to compare lists
//       and remove list items. The same result could be achieved with lots of
//       list.indexOf(...) if you need to avoid external libraries.

var vm = new Vue({
  data: {
    list: [
      {name: 'obj1 to watch'},
      {name: 'obj2 to watch'},
    ],
    watchTracker: [],
  },
  methods: {
    handleChange (newVal, oldVal) {
      // Handle changes here!
      console.log(newVal);
    },
    updateWatchers () {
      // Helper function for comparing list items to the "watchTracker".
      const getItem = (val) => val.item || val;

      // Items that aren't already watched: watch and add to watched list.
      _.differenceBy(this.list, this.watchTracker, getItem).forEach((item) => {
        const unwatch = this.$watch(() => item, this.handleChange, {deep: true});
        this.watchTracker.push({ item: item, unwatch: unwatch });
        // Uncomment below if adding a new item to the list should count as a "change".
        // this.handleChange(item);
      });

      // Items that no longer exist: unwatch and remove from the watched list.
      _.differenceBy(this.watchTracker, this.list, getItem).forEach((watchObj) => {
        watchObj.unwatch();
        _.pull(this.watchTracker, watchObj);
        // Optionally add any further cleanup in here for when items are removed.
      });
    },
  },
  watch: {
    list () {
      return this.updateWatchers();
    },
  },
  created () {
    return this.updateWatchers();
  },
});
Erik Koopmans
fuente
0

Ninguna de las respuestas para mí estaba funcionando. En realidad, si desea ver datos anidados con componentes que se llaman varias veces. Entonces se les llama con diferentes accesorios para identificarlos. Por ejemplo, <MyComponent chart="chart1"/> <MyComponent chart="chart2"/> mi solución es crear una variable de estado vuex adicional, que actualizo manualmente para señalar la propiedad que se actualizó por última vez.

Aquí hay un ejemplo de implementación de Vuex.ts:

export default new Vuex.Store({
    state: {
        hovEpacTduList: {},  // a json of arrays to be shared by different components, 
                             // for example  hovEpacTduList["chart1"]=[2,6,9]
        hovEpacTduListChangeForChart: "chart1"  // to watch for latest update, 
                                                // here to access "chart1" update 
   },
   mutations: {
        setHovEpacTduList: (state, payload) => {
            state.hovEpacTduListChangeForChart = payload.chart // we will watch hovEpacTduListChangeForChart
            state.hovEpacTduList[payload.chart] = payload.list // instead of hovEpacTduList, which vuex cannot watch
        },
}

En cualquier función Componente para actualizar la tienda:

    const payload = {chart:"chart1", list: [4,6,3]}
    this.$store.commit('setHovEpacTduList', payload);

Ahora en cualquier componente para obtener la actualización:

    computed: {
        hovEpacTduListChangeForChart() {
            return this.$store.state.hovEpacTduListChangeForChart;
        }
    },
    watch: {
        hovEpacTduListChangeForChart(chart) {
            if (chart === this.chart)  // the component was created with chart as a prop <MyComponent chart="chart1"/> 
                console.log("Update! for", chart, this.$store.state.hovEpacTduList[chart]);
        },
    },
thomasL
fuente