web-dev-qa-db-ja.com

Vue.jsでは、スロットのコンテンツが変更されたときにコンポーネントが検出できます

Vueにコンポーネントがあり、これはフレームサイズであり、ウィンドウサイズにスケーリングされ、(<slot>)要素(通常<img>または<canvas>)フレームに合わせてスケーリングし、その要素のパンとズームを有効にします。

コンポーネントは、要素が変更されたときに反応する必要があります。それを行うために確認できる唯一の方法は、それが発生したときに親がコンポーネントを生成することですが、コンポーネントが<slot>要素が変更され、それに応じて反応します。これを行う方法はありますか?

12
Euan Smith

私の知る限り、Vueはこれを行う方法を提供していません。しかし、ここで検討する価値のある2つのアプローチがあります。

スロットのDOMの変更を監視する

MutationObserver を使用して、<slot>のDOMがいつ変更されたかを検出します。これはコンポーネント間の通信を必要としません。コンポーネントのmountedコールバック中にオブザーバーを設定するだけです。

このアプローチの実際を示すスニペットを次に示します。

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>

放出の使用

Vueコンポーネントがスロットの変更を担当している場合、その変更が発生したときに イベントを発行 することをお勧めします。これにより、他のコンポーネントが発行された必要に応じてイベント。

これを行うには、空のVueインスタンスをグローバルイベントバスとして使用します。どのコンポーネントもイベントバス上のイベントをエミット/リッスンできます。この場合、親コンポーネントは "updated- content "イベント、そして子コンポーネントはそれに反応することができます。

以下に簡単な例を示します。

// 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>
11
asemahle

私が理解している限りVue 2+、コンポーネントはスロットのコンテンツが変更されたときに再レンダリングする必要があります。私の場合、いくつかのコンポーネントがなくなるまで非表示にする_error-message_コンポーネントがありました表示するスロットコンテンツ。最初に、このメソッドをコンポーネントのルート要素の_v-if_にアタッチしました(computedプロパティは機能しません、Vueは機能しません) _this.$slots_)に対する反応があるようです。

_checkForSlotContent() {
    let checkForContent = (hasContent, node) => {
        return hasContent || node.tag || (node.text && node.text.trim());
    }
    return this.$slots.default && this.$slots.default.reduce(checkForContent, false);
},
_

これは、DOM要素の追加や削除など、99%の変更がスロットで発生する場合は常に機能します。唯一のEdgeケースは次のような使用法でした:

_<error-message> {{someErrorStringVariable}} </error-message>
_

ここで更新されているのはテキストノードのみであり、理由はまだはっきりしていませんが、私のメソッドは起動しません。 beforeUpdate()created()をフックすることでこの問題を修正し、完全な解決策としてこれを残しました:

_<script>
    export default {
        data() {
            return {
                hasSlotContent: false,
            }
        },
        methods: {
            checkForSlotContent() {
                let checkForContent = (hasContent, node) => {
                    return hasContent || node.tag || (node.text && node.text.trim());
                }
                return this.$slots.default && this.$slots.default.reduce(checkForContent, false);
            },
        },
        beforeUpdate() {
            this.hasSlotContent = this.checkForSlotContent();
        },
        created() {
            this.hasSlotContent = this.checkForSlotContent();
        }
    };
</script>
_
4
Mathew Sonke

このトリックを検討することをお勧めします: https://codesandbox.io/s/1yn7nn72rl 、変更を監視してスロットコンテンツで何かをするために使用しました。

vuetify の動作VIconコンポーネントに触発されたアイデアは、ロジックを実装する機能コンポーネントを使用することですrender関数。 contextオブジェクトは、render関数の2番目の引数として渡されます。特に、contextオブジェクトには、スロットに対応するdataプロパティ(属性attrsを見つけることができます)とchildrenプロパティがあります。 (同じ結果でcontext.slot()関数をイベント呼び出しできます)。

宜しくお願いします

0
ekqnp