簡體   English   中英

在 Vue.js 中可以一個組件檢測插槽內容何時發生變化

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

我們在 Vue 中有一個組件,它是一個框架,縮放到 window 大小,它包含(在<slot>中)一個元素(通常是<img><canvas> ),它可以縮放以適應框架並啟用平移和縮放在那個元素上。

當元素發生變化時,組件需要做出反應。 我們可以看到的唯一方法是讓父組件在發生這種情況時刺激組件,但是如果組件能夠自動檢測<slot>元素何時發生變化並做出相應反應,那就更好了。 有沒有辦法做到這一點?

據我所知,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); } });
 .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>

據我了解 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>

還有另一種方法可以對插槽更改做出反應。 老實說,我覺得它更干凈,以防萬一。 對我來說,emit+event-bus 和突變觀察都不正確。

采取以下場景:

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

在這種情況下,當 someVariable 發生變化時,一些組件應該做出反應。 我在這里要做的是在組件上定義一個 :key,這會在 someVariable 更改時強制它重新呈現。

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

親切的問候 Rozbeh Chiryai Sharahi

我建議你考慮這個技巧: https ://codesandbox.io/s/1yn7nn72rl,我曾經用它來觀察變化並對插槽內容做任何事情。

這個想法受到 vuetify 的VIcon組件的工作原理的啟發,是使用一個函數式組件,我們在其render函數中實現邏輯。 context對象作為render函數的第二個參數傳遞。 特別是, context對象有一個data屬性(您可以在其中找到屬性attrs )和一個與插槽相對應的children屬性(您可以使用相同的結果調用context.slot()函數)。

此致

在帶有腳本設置語法的 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>

在此處閱讀更多信息: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM