Intentando recorrer una matriz de componentes Vue y acceder al elemento DOM de cada componente. Esto parece una necesidad bastante básica, pero he buscado mucho y, aunque hay muchas preguntas en la misma área general, no he encontrado una solución para esta.

Contexto: mi aplicación accederá al elemento DOM para diseñar el texto de acuerdo con la lógica de la aplicación. Para esta publicación, he creado una aplicación mínima y reducida para reproducir mi problema, por lo que no tiene ningún estilo, sino que simplemente muestra el texto interno del elemento.

JSFiddle o vea el mismo código a continuación. Verá que este código tiene éxito al acceder brevemente al componente $el, solo dentro del controlador de eventos $ montado. Después de eso no está definido. Podría guardar una referencia en el controlador de eventos, pero parece una locura pensar que sería necesario ... ¿qué me estoy perdiendo?

Html

<div id="app"
   oncontextmenu="return false" 
   v-on:mouseup="showchars()"
>
  <h2>The components a, b and c will be here once mounted. Note in the initial dialogs that their $el is accessible when their mount event handler calls showchars. Then click this div to call showchars again, and note that $el is no longer defined.</h2>
  <charcomponent 
    v-for = "c in charcomponents"
    v-bind:char="c.char"
   ></charcomponent>
</div>

Js

let CharComponent = {
    props: ['char'],
    mounted: function() {
      this.showinfo("it's like this on mount");
    },
    methods: {
      showinfo: function(cmt) {
        try {
          alert(cmt + ': char=' + this.char +', el=' + this.$el + ', and...');
          alert('...el.innerText=' + this.$el.innerText);
        }
        catch(error) {
            alert(error);
        }
      }  
    },
  template: '<p>{{ char }}</p>'  
}

var vm = new Vue({
  el: "#app",
  components: {
    charcomponent: CharComponent
   },
  data: {
    charcomponents: []
  },
  methods: {
    showchars() {
      for (var c of this.charcomponents) {
        c.showinfo("it's different later");      
      }
    }
  }
})

var ccharcomp = Vue.extend(CharComponent);

for (var ch of ['a','b','c']) {
    var ocharcomp = new ccharcomp({
    propsData: {
        char: ch
    }
  });
  vm.charcomponents.push(ocharcomp);
}
//alert(vm.chars.length);
0
Reg Edit 31 oct. 2019 a las 05:18

3 respuestas

La mejor respuesta

Puedo tratar de explicar lo que está sucediendo, aunque no estoy claro por qué estás haciendo lo que estás haciendo.

Comencemos aquí:

var ocharcomp = new ccharcomp({
  propsData: {
    char: ch
  }
});
vm.charcomponents.push(ocharcomp);

Esto está creando nuevas instancias del componente ccharcomp, que efectivamente es solo CharComponent. Cada uno de estos se agrega a la matriz.

Ninguna de estas instancias de Vue se está montando. Nunca se prestan. Simplemente se crean y se insertan en una matriz. No tendrán un $el y nunca se llamará al gancho mounted.

Entonces en su plantilla tiene esto:

<charcomponent 
  v-for = "c in charcomponents"
  v-bind:char="c.char"
></charcomponent>

Esto recorre esa misma matriz y crea una nueva instancia CharComponent para cada entrada. El valor de char se copia desde cada componente de la matriz al componente correspondiente creado en la plantilla.

Los componentes creados dentro de la plantilla serán renderizados, montados y tendrán un $el. Esto es lo que está viendo en su registro desde el gancho mounted.

Entonces tenemos esta:

showchars() {
  for (var c of this.charcomponents) {
    c.showinfo("it's different later");      
  }
}

Esto es un bucle sobre la matriz original de componentes, los que nunca fueron montados. Estos no tienen un $el, nunca los tienen. Los componentes creados por la plantilla aún existen y todavía tienen un $el pero no están en la matriz.

No puedo hacer una sugerencia concreta sobre cómo solucionar su problema, ya que realmente no entiendo por qué está creando los componentes secundarios de una manera tan extraña. El patrón más normal sería:

  1. Tenga la matriz ['a','b','c'] en data para el componente principal relevante.
  2. Pase sobre esa matriz en la plantilla para crear los hijos.
  3. Use ref en la plantilla y $refs en el padre para acceder a los componentes secundarios, luego use $el para tomar el elemento para cada niño.

Sin embargo, si solo desea aplicar un estilo, generalmente se desaconseja agarrar el elemento de esta manera. En su lugar, debe usar los enlaces :class o :style dentro de la plantilla para permitir que Vue aplique el estilo por usted. Es posible que deba introducir propiedades data adicionales para mantener el estado subyacente de modo que la plantilla pueda determinar qué estilos aplicar en dónde.

1
skirtle 31 oct. 2019 a las 02:58

Las respuestas muy informativas de @skirtle y @Jose Mari Ponce me ayudaron a ver por qué lo que tenía no estaba funcionando. Tengo la intención de mirar nuevamente el enlace de clase / estilo en la plantilla como una forma posiblemente mejor de lograr lo que esta aplicación en particular necesita, pero también quería seguir esto, ya que parece recorrer una lista conocida de componentes y acceder a $ el también ser necesario, ya que la lógica de la aplicación necesita determinar los valores a los que está vinculada la plantilla. También creo que es algo con una aplicabilidad mucho más amplia más allá de esta aplicación.

Resultó ser simplemente una cuestión de reemplazar mi HTML v-for con un JS appendChild, además de montar el componente creado dinámicamente en el nuevo elemento hijo:

Html

<div id="components">
</div>

Js

var ccharcomp = Vue.extend(CharComponent);
var container = document.getElementById("components");

for (var ch of ['a','b','c']) {
    var p = document.createElement("p");
    container.appendChild(p);
    var ocharcomp = new ccharcomp({
        propsData: {
            char: ch
        }
    });
    vm.charcomponents.push(ocharcomp.$mount(p));
}

JSFiddle

0
Reg Edit 1 nov. 2019 a las 12:32

Los componentes que está creando instancias a través de este método (declarativo):

<charcomponent 
  v-for = "c in charcomponents"
  v-bind:char="c.char"
></charcomponent>

Son diferentes de los que está creando aquí (programático):

var ccharcomp = Vue.extend(CharComponent);

for (var ch of ['a','b','c']) {
    var ocharcomp = new ccharcomp({
    propsData: {
        char: ch
    }
  });
  vm.charcomponents.push(ocharcomp);
}

La serie inicial de alertas fue activada por el gancho montado desde el primer conjunto de componentes (declarativo). Definitivamente, el objeto $ el está presente aquí debido a que el marcado ya forma parte del DOM y que también utilizó la declaración local que tiene en su instancia de Vue

 components: {
    charcomponent: CharComponent 
   },

Para el segundo conjunto de componentes, que se almacenó en una matriz llamada charcomponents, no tiene elementos montados. Tenga en cuenta que cuando usa Vue.extend, solo se crea una subclase de la instancia de Vue y, por lo tanto, los nuevos objetos instanciados deben montarse mediante el método $ mount.

showchars() {
      for (var c of this.charcomponents) {
        c.showinfo("it's different later");      
      }
    }

Efectivamente, al acceder al elemento $ el para el segundo lote de componentes, aún no está definido.

Creo una implementación simple aquí en JSFiddle para mostrar las diferentes implementaciones.

1
ecnop04 31 oct. 2019 a las 06:14