在Vue.js中,组件可以检测插槽内容何时更改

Eua*_*ith 10 html javascript vue.js vue-component

我们在Vue中有一个组件,它是一个框架,缩放到窗口大小,其中包含(在一个<slot>)元素(通常<img><canvas>),它可以缩放以适应框架,并启用该元素的平移和缩放.

当元素发生变化时,组件需要做出反应.我们可以看到这样做的唯一方法是父进程在发生这种情况时刺激组件,但是如果组件可以自动检测<slot>元素何时发生变化并做出相应反应,则会更好.有没有办法做到这一点?

ase*_*hle 12

据我所知,Vue没有提供这样做的方法.然而,有两种方法值得考虑.

观看Slot的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);
  }
});
Run Code Online (Sandbox Code Playgroud)
.content, .container {
  margin: 5px;
  border: 1px solid black;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

使用Emit

如果Vue组件负责更改插槽,则最好在 发生更改时发出事件.这允许任何其他组件在需要时响应发出的事件.

为此,请使用空Vue实例作为全局事件总线.任何组件都可以在事件总线上发出/侦听事件.在您的情况下,父组件可以发出"更新内容"事件,子组件可以对其做出反应.

这是一个简单的例子:

// 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);
  }
});
Run Code Online (Sandbox Code Playgroud)
.content, .container {
  margin: 5px;
  border: 1px solid black;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


Mat*_*nke 6

据我了解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);
},
Run Code Online (Sandbox Code Playgroud)

每当插槽中发生99%的更改(包括添加或删除DOM元素)时,此方法都可以很好地工作。唯一的极端情况是这样的用法:

<error-message> {{someErrorStringVariable}} </error-message>
Run Code Online (Sandbox Code Playgroud)

这里仅更新了一个文本节点,由于我仍然不清楚的原因,我的方法无法执行。我通过钩住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>
Run Code Online (Sandbox Code Playgroud)