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

新手上路,请多包涵

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

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

原文由 Euan Smith 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 451
2 个回答

据我所知,Vue 没有提供这样做的方法。但是,这里有两种方法值得考虑。

观察插槽的 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 实例作为全局事件总线。任何组件都可以发出/监听事件总线上的事件。在您的情况下,父组件可以发出“更新内容”事件,子组件可以对此做出反应。

这是一个简单的例子:

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

原文由 asemahle 发布,翻译遵循 CC BY-SA 4.0 许可协议

据我了解 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);
},

只要 99% 的更改发生在插槽中,包括任何 DOM 元素的添加或删除,这都适用。唯一的边缘情况是这样的用法:

 <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>

原文由 Mathew Sonke 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题