web-dev-qa-db-fra.com

Dans Vue.js, un composant peut-il détecter le moment où le contenu de la fente change?

Nous avons un composant dans Vue qui est un cadre, ajusté à la taille de la fenêtre, qui contient (dans un <slot>) un élément (généralement <img> ou <canvas>) qu’il redimensionne pour s’adapter au cadre et permet un panoramique et un zoom sur cet élément.

Le composant doit réagir lorsque l'élément change. La seule façon de le faire est que le parent prodigue le composant lorsque cela se produit. Toutefois, il serait beaucoup plus agréable que le composant puisse détecter automatiquement le moment où l'élément <slot> change et réagir en conséquence. Y a-t-il un moyen de faire cela?

8
Euan Smith

À ma connaissance, Vue ne permet pas de le faire. Cependant, voici deux approches qui méritent d'être considérées.

Regarder le DOM de la fente pour les changements

Utilisez un MutationObserver pour détecter quand le DOM dans <slot> change. Cela ne nécessite aucune communication entre les composants. Configurez simplement l'observateur lors du rappel mounted de votre composant.

Voici un extrait montrant cette approche en action:

Vue.component('container', {
  template: '#container',
  
  data: function() {
        return { number: 0, observer: null }
  },
  
  mounted: function() {
    // Create the observer (and what to do on changes...)
    this.observer = new MutationObserver(function(mutations) {
      this.number++;
    }.bind(this));
    
    // Setup the observer
    this.observer.observe(
      $(this.$el).find('.content')[0],
      { attributes: true, childList: true, characterData: true, subtree: true }
    );
  },
  
  beforeDestroy: function() {
    // Clean up
    this.observer.disconnect();
  }
});

var app = new Vue({
  el: '#app',

  data: { number: 0 },
  
  mounted: function() {
    //Update the element in the slot every second
    setInterval(function(){ this.number++; }.bind(this), 1000);
  }
});
.content, .container {
  margin: 5px;
  border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<template id="container">
  <div class="container">
    I am the container, and I have detected {{ number }} updates.
    <div class="content"><slot></slot></div>
  </div>
</template>

<div id="app">
  
  <container>
    I am the content, and I have been updated {{ number }} times.
  </container>
  
</div>

Utiliser Emit

Si un composant Vue est responsable de la modification de l'emplacement, il est préférable de émettre un événement lorsque cette modification se produit. Cela permet à tout autre composant de répondre à l'événement émis si nécessaire.

Pour ce faire, utilisez une instance Vue vide en tant que bus d'événements global. Tout composant peut émettre/écouter des événements sur le bus d’événements. Dans votre cas, le composant parent peut émettre un événement "updated-content" et le composant enfant peut y réagir.

Voici un exemple simple:

// Use an empty Vue instance as an event bus
var bus = new Vue()

Vue.component('container', {
  template: '#container',

  data: function() {
    return { number: 0 }
  },

  methods: {
    increment: function() { this.number++; }
  },
  
  created: function() {
    // listen for the 'updated-content' event and react accordingly
    bus.$on('updated-content', this.increment);
  },
  
  beforeDestroy: function() {
    // Clean up
    bus.$off('updated-content', this.increment);
  }
});

var app = new Vue({
  el: '#app',

  data: { number: 0 },

  mounted: function() {
    //Update the element in the slot every second, 
    //  and emit an "updated-content" event
    setInterval(function(){ 
      this.number++;
      bus.$emit('updated-content');
    }.bind(this), 1000);
  }
});
.content, .container {
  margin: 5px;
  border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<template id="container">
  <div class="container">
    I am the container, and I have detected {{ number }} updates.
    <div class="content">
      <slot></slot>
    </div>
  </div>
</template>

<div id="app">

  <container>
    I am the content, and I have been updated {{ number }} times.
  </container>
  
</div>
9
asemahle

Je vous suggérerais d’envisager cette astuce: https://codesandbox.io/s/1yn7nn72rl , que j’avais l'habitude de surveiller les modifications et de faire quoi que ce soit avec un contenu de fente.

L'idée, inspirée par le fonctionnement de composant VIcon de vuetify , consiste à utiliser un composant fonctionnel dans lequel nous implémentons la logique dans sa fonction render. Un objet context est passé en tant que deuxième argument de la fonction render. En particulier, l'objet context a une propriété data (dans laquelle vous pouvez trouver des attributs, attrs) et une propriété children, correspondant à l'emplacement (vous pouvez éventuellement appeler la fonction context.slot() avec le même résultat).

Meilleures salutations

0
ekqnp