简体   繁体   English

在 Vue.js 中可以一个组件检测插槽内容何时发生变化

[英]In Vue.js can a component detect when the slot content changes

We have a component in Vue which is a frame, scaled to the window size, which contains (in a <slot> ) an element (typically <img> or <canvas> ) which it scales to fit the frame and enables pan and zoom on that element.我们在 Vue 中有一个组件,它是一个框架,缩放到 window 大小,它包含(在<slot>中)一个元素(通常是<img><canvas> ),它可以缩放以适应框架并启用平移和缩放在那个元素上。

The component needs to react when the element changes.当元素发生变化时,组件需要做出反应。 The only way we can see to do it is for the parent to prod the component when that happens, however it would be much nicer if the component could automatically detect when the <slot> element changes and react accordingly.我们可以看到的唯一方法是让父组件在发生这种情况时刺激组件,但是如果组件能够自动检测<slot>元素何时发生变化并做出相应反应,那就更好了。 Is there a way to do this?有没有办法做到这一点?

To my knowledge, Vue does not provide a way to do this.据我所知,Vue 没有提供这样做的方法。 However here are two approaches worth considering.然而,这里有两种方法值得考虑。

Watching the Slot's DOM for Changes查看 Slot 的 DOM 以了解更改

Use a MutationObserver to detect when the DOM in the <slot> changes.使用MutationObserver来检测<slot>中的 DOM 何时发生变化。 This requires no communication between components.这不需要组件之间的通信。 Simply set up the observer during the mounted callback of your component.只需在组件的mounted回调期间设置观察者。

Here's a snippet showing this approach in 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>

Using Emit使用发射

If a Vue component is responsible for changing the slot, then it is best to emit an event when that change occurs.如果 Vue 组件负责更改插槽,那么最好在更改发生时发出事件 This allows any other component to respond to the emitted event if needed.这允许任何其他组件在需要时响应发出的事件。

To do this, use an empty Vue instance as a global event bus.为此,请使用一个空的 Vue 实例作为全局事件总线。 Any component can emit/listen to events on the event bus.任何组件都可以在事件总线上发出/监听事件。 In your case, the parent component could emit an "updated-content" event, and the child component could react to it.在您的情况下,父组件可能会发出“更新内容”事件,而子组件可能会对它做出反应。

Here is a simple example:这是一个简单的例子:

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

As far as I understand Vue 2+, a component should be re-rendered when the slot content changes.据我了解 Vue 2+,当插槽内容发生变化时,应该重新渲染组件。 In my case I had an error-message component that should hide until it has some slot content to show.在我的情况下,我有一个error-message组件,它应该隐藏,直到它有一些插槽内容要显示。 At first I had this method attached to v-if on my component's root element (a computed property won't work, Vue doesn't appear to have reactivity on this.$slots ).起初,我将此方法附加到组件根元素的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);
},

This works well whenever 99% of changes happen in the slot, including any addition or removal of DOM elements.只要插槽中发生 99% 的更改(包括任何添加或删除 DOM 元素),这都会很好地工作。 The only edge case was usage like this:唯一的边缘情况是这样的用法:

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

Only a text node is being updated here, and for reasons still unclear to me, my method wouldn't fire.这里只更新了一个文本节点,由于我仍然不清楚的原因,我的方法不会触发。 I fixed this case by hooking into beforeUpdate() and created() , leaving me with this for a full solution:我通过连接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>

There is another way to react on slot changes.还有另一种方法可以对插槽更改做出反应。 I find it much cleaner to be honest in case it fits.老实说,我觉得它更干净,以防万一。 Neither emit+event-bus nor mutation observing seems correct to me.对我来说,emit+event-bus 和突变观察都不正确。

Take following scenario:采取以下场景:

<some-component>{{someVariable}}</some-component>

In this case when someVariable changes some-component should react.在这种情况下,当 someVariable 发生变化时,一些组件应该做出反应。 What I'd do here is defining a :key on the component, which forces it to rerender whenever someVariable changes.我在这里要做的是在组件上定义一个 :key,这会在 someVariable 更改时强制它重新呈现。

<some-component :key="someVariable">Some text {{someVariable}}</some-component>

Kind regard Rozbeh Chiryai Sharahi亲切的问候 Rozbeh Chiryai Sharahi

I would suggest you to consider this trick: https://codesandbox.io/s/1yn7nn72rl , that I used to watch changes and do anything with slot content.我建议你考虑这个技巧: https ://codesandbox.io/s/1yn7nn72rl,我曾经用它来观察变化并对插槽内容做任何事情。

The idea, inspired by how works VIcon component of vuetify , is to use a functional component in which we implement logic in its render function.这个想法受到 vuetify 的VIcon组件的工作原理的启发,是使用一个函数式组件,我们在其render函数中实现逻辑。 A context object is passed as the second argument of the render function. context对象作为render函数的第二个参数传递。 In particular, the context object has a data property (in which you can find attributes, attrs ), and a children property, corresponding to the slot (you could event call the context.slot() function with the same result).特别是, context对象有一个data属性(您可以在其中找到属性attrs )和一个与插槽相对应的children属性(您可以使用相同的结果调用context.slot()函数)。

Best regards此致

In Vue 3 with script setup syntax, I used the MutationObserver to great success:在带有脚本设置语法的 Vue 3 中,我使用 MutationObserver 取得了巨大成功:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const container = ref();
const mutationObserver = ref(null);
const mockData = ref([]);

const desiredFunc = () => {
    console.log('children changed');
};

const connectMutationObserver = () => {
    mutationObserver.value = new MutationObserver(desiredFunc);

    mutationObserver.value.observe(container.value, {
        attributes: true,
        childList: true,
        characterData: true,
        subtree: true,
    });
};

const disconnectMutationObserver = () => {
    mutationObserver.value.disconnect();
};

onMounted(async () => {
    connectMutationObserver();

    setTimeout(() => { mockData.value = [1, 2, 3]; }, 5000);
});

onUnmounted(() => {
    disconnectMutationObserver();
});
</script>

<template>
    <div ref="container">
        <div v-for="child in mockData" :key="child">
            {{ child }}
        </div>
    </div>
</template>

Read more here: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver在此处阅读更多信息: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Vue.js 中为子组件中的迭代槽迭代槽内容 - Iterating slot content for iterated slots in child component in Vue.js Vue.js 3 - 将组件插入插槽 - Vue.js 3 - inserting component into slot Vue.js 将数据从插槽传递到组件实例 - Vue.js pass data from slot into component instance Vue.js不能用来 <slot> 渲染父组件 - Vue.js cannot use to <slot> to render the parent component Vue.js - 如何将道具传递给带有插槽的组件元素 - Vue.js - how to pass props to component elements with slot 如何在Vue.js中删除组件及其插槽 - How to remove component and its slot in Vue.js 无法将属性传递给vue.js中插槽中的组件? - Cannot pass properties to a component in a slot in vue.js? 最佳替代品<slot />在<textarea><i>in [ Vue.js 3 ] component</div></i><b>在 [ Vue.js 3 ] 组件中</div></b></textarea><div id="text_translate"><p> 我想用 textarea 创建组件并在其中传递数据</p><pre><c-textarea> hello world </c-textarea></pre><p> 但是经典的<slot/>标签在 textarea 内部不起作用什么是最简单和最干净的选择</p><pre><template> <textarea><slot/></textarea> </template></pre><p> 在 Vue.js 3</p></div> - best alternative for <slot/> in <textarea> in [ Vue.js 3 ] component Vue.js 插槽 - 如何在计算属性中检索插槽内容 - Vue.js slots - how to retrieve slot content in computed properties vue.js 和属性中的插槽 - vue.js and slot in attribute
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM