在 Vue.js 中,组件是否可以检测插槽内容何时更改
In Vue.js can a component detect when the slot content changes
我们在 Vue 中有一个组件,它是一个框架,缩放到 window 大小,其中包含(在 <slot>
中)一个元素(通常是 <img>
或 <canvas>
) 它缩放以适合框架并启用对该元素的平移和缩放。
组件需要在元素变化时做出反应。我们能看到的唯一方法是让父组件在发生这种情况时触发组件,但是如果组件能够自动检测 <slot>
元素何时发生变化并做出相应的反应,那就更好了。有没有办法做到这一点?
据我所知,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 组件负责更改插槽,那么最好在更改发生时 emit an event。这允许任何其他组件在需要时响应发出的事件。
为此,请使用一个空的 Vue 实例作为全局事件总线。任何组件都可以 emit/listen 事件总线上的事件。在您的情况下,父组件可以发出 "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>
我建议您考虑这个技巧:https://codesandbox.io/s/1yn7nn72rl,我曾经观察变化并对广告位内容做任何事情。
受 vuetify 的 VIcon 组件 工作原理的启发,我们的想法是使用功能组件,我们在其 render
函数中实现逻辑. context
对象作为 render
函数的第二个参数传递。特别是,context
对象有一个data
属性(你可以在其中找到属性,attrs
),和一个children
属性,对应于插槽(您可以事件调用 context.slot()
函数得到相同的结果)。
此致
据我了解Vue 2+,组件应该在slot内容改变时重新渲染。在我的例子中,我有一个 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 和 mutation observing 对我来说都不正确。
采用以下场景:
<some-component>{{someVariable}}</some-component>
在这种情况下,当 someVariable 发生变化时,某些组件应该做出反应。我在这里要做的是在组件上定义一个 :key ,它会强制它在 someVariable 更改时重新呈现。
<some-component :key="someVariable">Some text {{someVariable}}</some-component>
亲切的问候
Rozbeh Chiryai Sharahi
我们在 Vue 中有一个组件,它是一个框架,缩放到 window 大小,其中包含(在 <slot>
中)一个元素(通常是 <img>
或 <canvas>
) 它缩放以适合框架并启用对该元素的平移和缩放。
组件需要在元素变化时做出反应。我们能看到的唯一方法是让父组件在发生这种情况时触发组件,但是如果组件能够自动检测 <slot>
元素何时发生变化并做出相应的反应,那就更好了。有没有办法做到这一点?
据我所知,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 组件负责更改插槽,那么最好在更改发生时 emit an event。这允许任何其他组件在需要时响应发出的事件。
为此,请使用一个空的 Vue 实例作为全局事件总线。任何组件都可以 emit/listen 事件总线上的事件。在您的情况下,父组件可以发出 "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>
我建议您考虑这个技巧:https://codesandbox.io/s/1yn7nn72rl,我曾经观察变化并对广告位内容做任何事情。
受 vuetify 的 VIcon 组件 工作原理的启发,我们的想法是使用功能组件,我们在其 render
函数中实现逻辑. context
对象作为 render
函数的第二个参数传递。特别是,context
对象有一个data
属性(你可以在其中找到属性,attrs
),和一个children
属性,对应于插槽(您可以事件调用 context.slot()
函数得到相同的结果)。
此致
据我了解Vue 2+,组件应该在slot内容改变时重新渲染。在我的例子中,我有一个 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 和 mutation observing 对我来说都不正确。
采用以下场景:
<some-component>{{someVariable}}</some-component>
在这种情况下,当 someVariable 发生变化时,某些组件应该做出反应。我在这里要做的是在组件上定义一个 :key ,它会强制它在 someVariable 更改时重新呈现。
<some-component :key="someVariable">Some text {{someVariable}}</some-component>
亲切的问候 Rozbeh Chiryai Sharahi