如何使用 Vue js 2 在组件子组件链上冒泡事件?
How to bubble events on a component subcomponent chain with Vue js 2?
我的 vue 应用程序使用:
component-parent 由 component-child 构成的组件
在 component-parent 里面我有按钮,当有人点击一个按钮时我想发出一个事件以便由 vue 处理并传递给另一个组件
到目前为止我做了什么:
var vm = new Vue({
el: '#app',
methods: {
itemSelectedListener: function(item){
console.log('itemSelectedListener', item);
}
}
});
Vue.component('component-child', {
template: ' <span v-on:click="chooseItem(pty )" >Button </span>',
methods: {
chooseItem: function(pty){
console.log(pty);
this.$emit('itemSelected', {
'priority' : pty
});
}
}
});
Vue.component('component-parent', {
template: '<component-child v-for="q in items" ></component-child>'
});
HTML:
<component-parent v-on:itemSelected="itemSelectedListener" ></component-parent>
它到达了我的 console.log(pty);
线路,但似乎 this.$emit('itemSelected'
无法接通:
console.log('itemSelectedListener', item); // this is not going to be called...
提示?
我应该从 child->parent->Vue-instance 冒泡事件吗? (我也试过了,但没有成功)
您的 component-parent
模板存在一个问题,因为它试图呈现多个子组件。 Vue 通常在组件内部需要一个根 div,因此您需要将其包装在 div 或其他标记中。
<div>
<component-child v-for="q in items"></component-child>
</div>
要指出的第二件事是,您从下级 2 级的子组件发出事件,并在根中收听它。
Root //but you listen to the event up here 1 level above
Component 1 //you should listen to the event here
Component 2 //your try to emit it from here
这里有 2 个选项。从 component-child
发出,在 component-parent
中监听该事件,然后向上传播该事件。 Fiddlehttps://jsfiddle.net/bjqwh74t/29/
第二个选项是注册一个名为 bus
的全局变量,它是一个空的 vue 实例,当您想要在非 child-parent 组件之间进行通信时,您可以使用它。 Fiddlehttps://jsfiddle.net/bjqwh74t/30/
通常在父组件和子组件之间,您可以直接使用事件,方法是从子组件发出并在父组件中使用 v-on:event-name="handler"
监听,但对于组件之间有更多级别的情况,您使用第二种方法。
文档 link 第一种情况:https://vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events
文档link第二种情况:https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
PS:更喜欢使用 kebab-case 作为事件名称,这意味着您使用 -
而不是大写字母。用大写字母书写会导致您的事件未在根中捕获的奇怪情况。
有点晚了,但我是这样做的:
子组件:
this.$root.$emit('foobar',{...});
父组件:
this.$root.$on('foobar')
在您的子组件中,只需使用 $emit
将事件发送到 $root
,如下所示:
v-on:click="$root.$emit('hamburger-click')"
然后,在您的父组件中(例如:"App"),在 Vue mounted
生命周期挂钩中设置侦听器,如下所示:
export default {
<snip...>
mounted: function() {
this.$root.$on('hamburger-click', function() {
console.log(`Hamburger clicked!`);
});
}
}
您可以使用浏览器的事件 API。它需要比 Vue 的内置内容多一点的脚本,但它也可以让您解决这些冒泡问题(并且与创建“总线”的代码量大致相同,如已接受的答案)。
在子组件上:
this.$el.dispatchEvent(new CustomEvent('itemSelected', { detail: { 'priority' : pty }, bubbles: true, composed: true });
在父组件上,在 mounted
生命周期部分:
mounted() {
this.$el.addEventListener('itemSelected', e => console.log('itemSelectedListener', e.detail));
}
创建一个custome directive用于冒泡,并在子组件中使用。
示例指令:
// Add this to main.ts when initializing Vue
Vue.directive('bubble', {
bind(el, { arg }, {context, componentInstance}) {
if (!componentInstance || !context || !arg) {
return;
}
// bubble the event to the parent
componentInstance.$on(v, context.$emit.bind(context, arg));
}
});
子组件可以使用该指令通过父组件发出(我切换到 kabob 大小写和 shorthand 事件)。
<!-- template for component-parent -->
<component-child v-bubble:item-selected v-for="q in items"></component-child>
<!-- usage of component-parent -->
<component-parent @:item-selected="itemSelectedListener"></component-parent>
包括我下面的完整绑定指令。它允许冒泡多个事件。
<!--------------------
* A few examples
--------------------->
<!-- bubble single event -->
<child v-bubble:click/>
<child v-bubble:_.click/>
<child v-bubble="'click'"/>
<child v-bubble:any-costume-event-will-work/>
<!-- bubble: click, focus, blur -->
<child v-bubble:_.click.focus.blur/>
<child v-bubble="'click, focus, blur'"/>
<!-- prefixed bubbling: click, focus, blur as child-click, child-focus, child-blur -->
<child v-bubble:child.click.focus.blur/>
<child v-bubble:child="'click, focus, blur'"/>
Vue.directive('bubble', {
bind(el, { value, arg: prefix = '', modifiers }, {context, componentInstance}) {
const events = value && value.trim() ? value.split(',') : Object.keys(modifiers);
if (!events.length && prefix) {
events.push(prefix);
prefix = '';
} else if(prefix) {
prefix = prefix === '_' ? '' : prefix += '-';
}
if (!componentInstance || !context || !events.length) {
return;
}
events.forEach((v: string) => {
v = v.trim();
const eventName = `${prefix}${v}`;
const bubble = context.$emit.bind(context, eventName);
componentInstance.$on(v, bubble);
});
}
});
我有点惊讶没有人建议使用事件总线组件。在高度解耦的系统中,共享事件总线是一种相当常见的模式,然后使用它 link 多个断开连接的组件 pub/sub 样式。
//eventbus.js
import Vue from 'vue'
export const EventBus = new Vue()
有了之后,从任何地方发布活动都很简单
// component1.js
import { EventBus } from '@/services/eventbus'
...
EventBus.$emit('do-the-things')
并从其他地方聆听他们的声音。
// component2.js
import { EventBus } from '@/services/eventbus'
...
EventBus.$on('do-the-things', this.doAllTheThings)
请注意,这两个组件彼此之间一无所知,它们实际上不需要关心事件是如何或为何引发的。
这种方法有一些潜在的不良副作用。事件名称确实需要全局唯一,这样您就不会混淆您的应用程序。除非您做一些更复杂的事情,否则您还可能通过单个对象引导每个事件。您可以对自己的应用程序源进行成本/收益分析,看看它是否适合您。
我的 vue 应用程序使用:
component-parent 由 component-child 构成的组件
在 component-parent 里面我有按钮,当有人点击一个按钮时我想发出一个事件以便由 vue 处理并传递给另一个组件
到目前为止我做了什么:
var vm = new Vue({
el: '#app',
methods: {
itemSelectedListener: function(item){
console.log('itemSelectedListener', item);
}
}
});
Vue.component('component-child', {
template: ' <span v-on:click="chooseItem(pty )" >Button </span>',
methods: {
chooseItem: function(pty){
console.log(pty);
this.$emit('itemSelected', {
'priority' : pty
});
}
}
});
Vue.component('component-parent', {
template: '<component-child v-for="q in items" ></component-child>'
});
HTML:
<component-parent v-on:itemSelected="itemSelectedListener" ></component-parent>
它到达了我的 console.log(pty);
线路,但似乎 this.$emit('itemSelected'
无法接通:
console.log('itemSelectedListener', item); // this is not going to be called...
提示?
我应该从 child->parent->Vue-instance 冒泡事件吗? (我也试过了,但没有成功)
您的 component-parent
模板存在一个问题,因为它试图呈现多个子组件。 Vue 通常在组件内部需要一个根 div,因此您需要将其包装在 div 或其他标记中。
<div>
<component-child v-for="q in items"></component-child>
</div>
要指出的第二件事是,您从下级 2 级的子组件发出事件,并在根中收听它。
Root //but you listen to the event up here 1 level above
Component 1 //you should listen to the event here
Component 2 //your try to emit it from here
这里有 2 个选项。从 component-child
发出,在 component-parent
中监听该事件,然后向上传播该事件。 Fiddlehttps://jsfiddle.net/bjqwh74t/29/
第二个选项是注册一个名为 bus
的全局变量,它是一个空的 vue 实例,当您想要在非 child-parent 组件之间进行通信时,您可以使用它。 Fiddlehttps://jsfiddle.net/bjqwh74t/30/
通常在父组件和子组件之间,您可以直接使用事件,方法是从子组件发出并在父组件中使用 v-on:event-name="handler"
监听,但对于组件之间有更多级别的情况,您使用第二种方法。
文档 link 第一种情况:https://vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events
文档link第二种情况:https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
PS:更喜欢使用 kebab-case 作为事件名称,这意味着您使用 -
而不是大写字母。用大写字母书写会导致您的事件未在根中捕获的奇怪情况。
有点晚了,但我是这样做的:
子组件:
this.$root.$emit('foobar',{...});
父组件:
this.$root.$on('foobar')
在您的子组件中,只需使用 $emit
将事件发送到 $root
,如下所示:
v-on:click="$root.$emit('hamburger-click')"
然后,在您的父组件中(例如:"App"),在 Vue mounted
生命周期挂钩中设置侦听器,如下所示:
export default {
<snip...>
mounted: function() {
this.$root.$on('hamburger-click', function() {
console.log(`Hamburger clicked!`);
});
}
}
您可以使用浏览器的事件 API。它需要比 Vue 的内置内容多一点的脚本,但它也可以让您解决这些冒泡问题(并且与创建“总线”的代码量大致相同,如已接受的答案)。
在子组件上:
this.$el.dispatchEvent(new CustomEvent('itemSelected', { detail: { 'priority' : pty }, bubbles: true, composed: true });
在父组件上,在 mounted
生命周期部分:
mounted() {
this.$el.addEventListener('itemSelected', e => console.log('itemSelectedListener', e.detail));
}
创建一个custome directive用于冒泡,并在子组件中使用。
示例指令:
// Add this to main.ts when initializing Vue
Vue.directive('bubble', {
bind(el, { arg }, {context, componentInstance}) {
if (!componentInstance || !context || !arg) {
return;
}
// bubble the event to the parent
componentInstance.$on(v, context.$emit.bind(context, arg));
}
});
子组件可以使用该指令通过父组件发出(我切换到 kabob 大小写和 shorthand 事件)。
<!-- template for component-parent -->
<component-child v-bubble:item-selected v-for="q in items"></component-child>
<!-- usage of component-parent -->
<component-parent @:item-selected="itemSelectedListener"></component-parent>
包括我下面的完整绑定指令。它允许冒泡多个事件。
<!--------------------
* A few examples
--------------------->
<!-- bubble single event -->
<child v-bubble:click/>
<child v-bubble:_.click/>
<child v-bubble="'click'"/>
<child v-bubble:any-costume-event-will-work/>
<!-- bubble: click, focus, blur -->
<child v-bubble:_.click.focus.blur/>
<child v-bubble="'click, focus, blur'"/>
<!-- prefixed bubbling: click, focus, blur as child-click, child-focus, child-blur -->
<child v-bubble:child.click.focus.blur/>
<child v-bubble:child="'click, focus, blur'"/>
Vue.directive('bubble', {
bind(el, { value, arg: prefix = '', modifiers }, {context, componentInstance}) {
const events = value && value.trim() ? value.split(',') : Object.keys(modifiers);
if (!events.length && prefix) {
events.push(prefix);
prefix = '';
} else if(prefix) {
prefix = prefix === '_' ? '' : prefix += '-';
}
if (!componentInstance || !context || !events.length) {
return;
}
events.forEach((v: string) => {
v = v.trim();
const eventName = `${prefix}${v}`;
const bubble = context.$emit.bind(context, eventName);
componentInstance.$on(v, bubble);
});
}
});
我有点惊讶没有人建议使用事件总线组件。在高度解耦的系统中,共享事件总线是一种相当常见的模式,然后使用它 link 多个断开连接的组件 pub/sub 样式。
//eventbus.js
import Vue from 'vue'
export const EventBus = new Vue()
有了之后,从任何地方发布活动都很简单
// component1.js
import { EventBus } from '@/services/eventbus'
...
EventBus.$emit('do-the-things')
并从其他地方聆听他们的声音。
// component2.js
import { EventBus } from '@/services/eventbus'
...
EventBus.$on('do-the-things', this.doAllTheThings)
请注意,这两个组件彼此之间一无所知,它们实际上不需要关心事件是如何或为何引发的。
这种方法有一些潜在的不良副作用。事件名称确实需要全局唯一,这样您就不会混淆您的应用程序。除非您做一些更复杂的事情,否则您还可能通过单个对象引导每个事件。您可以对自己的应用程序源进行成本/收益分析,看看它是否适合您。