Vue.js 2.0 似乎不会将事件从孙子发送到他的祖父组件。
Vue.component('parent', {
template: '<div>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
data(){
return {
action: 'No action'
}
},
methods: {
performAction() { this.action = 'actionDone' }
}
})
Vue.component('child', {
template: '<div>I am the child <grand-child></grand-child></div>'
})
Vue.component('grand-child', {
template: '<div>I am the grand-child <button @click="doEvent">Do Event</button></div>',
methods: {
doEvent() { this.$emit('eventtriggered') }
}
})
new Vue({
el: '#app'
})
这个 JsFiddle 解决了问题 https://jsfiddle.net/y5dvkqbd/4/ ,但是通过 emtting 两个事件:
一个从孙子到中间组件
然后再次从中间组件发射到祖父母
添加这个中间事件似乎是重复的和不必要的。有没有办法直接发射给我不知道的祖父母?
Vue 2.4 引入了一种使用 vm.$listeners
轻松将事件向上传递的方法
从 https://v2.vuejs.org/v2/api/#vm-listeners :
包含父范围的 v-on 事件侦听器(没有 .native 修饰符)。这可以通过 v-on="$listeners" 传递给内部组件 - 在创建透明包装组件时很有用。
在 child
模板的 grand-child
组件中使用 v-on="$listeners"
查看下面的代码段:
Vue.component('parent', { template: '
我是父级。值为{{displayValue}}。
' + '我是孩子。我只是提供一些 UI 的包装器。
' + '我是孙子:' + '
' + '
新答案(2018 年 11 月更新)
我发现我们实际上可以通过利用大子组件中的 $parent
属性来做到这一点:
this.$parent.$emit("submit", {somekey: somevalue})
更干净,更简单。
Vue 社区普遍倾向于使用 Vuex 来解决此类问题。对 Vuex 状态进行了更改,并且 DOM 表示只是从中流出,在许多情况下消除了对事件的需求。
除此之外,重新发射可能是下一个最佳选择,最后您可能会选择使用事件总线,如对该问题的另一个高度投票的答案中所详述。
下面的答案是我对这个问题的原始答案,而不是我现在会采用的方法,因为我对 Vue 有更多的经验。
这是我可能不同意 Vue 的设计选择并诉诸 DOM 的情况。
在 grand-child
中,
methods: {
doEvent() {
try {
this.$el.dispatchEvent(new Event("eventtriggered"));
} catch (e) {
// handle IE not supporting Event constructor
var evt = document.createEvent("Event");
evt.initEvent("eventtriggered", true, false);
this.$el.dispatchEvent(evt);
}
}
}
在 parent
中,
mounted(){
this.$el.addEventListener("eventtriggered", () => this.performAction())
}
否则,是的,您必须重新发送或使用公共汽车。
注意:我在doEvent方法中添加了处理IE的代码;该代码可以以可重用的方式提取。
是的,你是正确的事件只从孩子到父母。他们不会走得更远,例如从孩子到祖父母。
Vue 文档(简要)在 Non Parent-Child Communication 部分解决了这种情况。
一般的想法是在祖父组件中创建一个空的 Vue
组件,该组件通过道具从祖父传递给子孙。然后,祖父母侦听事件,而孙子女在该“事件总线”上发出事件。
一些应用程序使用全局事件总线而不是每个组件的事件总线。使用全局事件总线意味着您将需要具有唯一的事件名称或命名空间,以便事件不会在不同组件之间发生冲突。
以下是 how to implement a simple global event bus 的示例。
如果您想灵活地简单地将事件广播给所有父母及其父母,直到根,您可以执行以下操作:
let vm = this.$parent
while(vm) {
vm.$emit('submit')
vm = vm.$parent
}
另一种解决方案将在根节点开启/发射:
在 grand-child 中使用 vm.$root.$emit
,然后在祖先(或您想要的任何地方)中使用 vm.$root.$on
。
更新:有时您希望在某些特定情况下禁用侦听器,请使用 vm.$off(例如:vm.$root.off('event-name')
inside生命周期挂钩=beforeDestroy)。
Vue.component('parent', { template: '
我根据@digout 的回答做了一个简短的混合。你想把它放在你的 Vue 实例初始化(新的 Vue ...)之前,以便在项目中全局使用它。您可以像正常事件一样使用它。
Vue.mixin({
methods: {
$propagatedEmit: function (event, payload) {
let vm = this.$parent;
while (vm) {
vm.$emit(event, payload);
vm = vm.$parent;
}
}
}
})
targetRef
,它会停止在您定位的组件上的传播。然后,while
条件将包含 && vm.$refs[targetRef]
- 您还需要在目标组件上包含该 ref
属性 在我的用例中,我不需要一直隧道到根,节省了一些事件的触发时间,也许还有几个宝贵的纳秒时间
VueJS 2 组件具有包含其父组件的 $parent
属性。
该父组件还包括其自己的 $parent
属性。
然后,访问“祖父母”组件就是访问“父母的父”组件:
this.$parent["$parent"].$emit("myevent", { data: 123 });
无论如何,这有点棘手,我建议使用像 Vuex 这样的全局状态管理器或类似工具,正如其他响应者所说的那样。
这是我使用 event bus 的唯一情况!!用于将数据从深层嵌套子级传递到非直接父级通信。
首先:使用以下内容创建一个 js 文件(我将其命名为 eventbus.js):
import Vue from 'vue'
Vue.prototype.$event = new Vue()
第二:在您的子组件中发出一个事件:
this.$event.$emit('event_name', 'data to pass')
第三:在父母听那个事件:
this.$event.$on('event_name', (data) => {
console.log(data)
})
注意:如果您不再想要该活动,请取消注册:
this.$event.$off('event_name')
信息:无需阅读以下个人意见
我不喜欢使用 vuex 进行孙子与祖父母的交流(或类似的交流级别)。
在 vue.js 中用于将数据从祖父母传递给孙子女,您可以使用提供/注入。但是相反的东西没有类似的东西。 (孙子到祖父)所以每当我必须进行这种通信时,我都会使用事件总线。
引用@kubaklam 和@digout 的答案,这是我用来避免在孙子和(可能是遥远的)祖父母之间的每个父组件上发射的方法:
{
methods: {
tunnelEmit (event, ...payload) {
let vm = this
while (vm && !vm.$listeners[event]) {
vm = vm.$parent
}
if (!vm) return console.error(`no target listener for event "${event}"`)
vm.$emit(event, ...payload)
}
}
}
当构建一个包含遥远的孙子的组件时,您不希望将许多/任何组件绑定到存储,但希望根组件充当存储/事实来源,这非常有效。这类似于 Ember 的数据向下操作哲学。不利的一面是,如果您想在其间的每个父母身上监听该事件,那么这将行不通。但是你可以像上面@kubaklam 的回答一样使用 $propogateEmit 。
编辑:初始 vm 应该设置为组件,而不是组件的父级。即 let vm = this
而不是 let vm = this.$parent
我通过创建一个绑定到窗口的类并简化广播/侦听设置以在 Vue 应用程序中的任何位置工作,从而真正挖掘了处理这种情况的方式。
window.Event = new class {
constructor() {
this.vue = new Vue();
}
fire(event, data = null) {
this.vue.$emit(event, data);
}
listen() {
this.vue.$on(event, callback);
}
}
现在你可以通过调用从任何地方发射/广播/任何东西:
Event.fire('do-the-thing');
...您可以通过以下方式聆听父母,祖父母的任何声音:
Event.listen('do-the-thing', () => {
alert('Doing the thing!');
});
从 Vue 3 开始,根事件发生了一些根本性的变化:
$on
、$off
和 $once
根方法不再存在。在某种程度上可以替换它,因为您可以listen to root events这样做:
createApp(App, {
// Listen for the 'expand' event
onExpand() {
console.log('expand')
}
})
另一个解决方案是事件总线,但 Vue.js 文档的观点很模糊——从长远来看,它们可能会导致维护问题。您可能会得到一组不断扩展的发射和事件接收器,但对于如何管理它或哪些组件可能在其他地方受到影响没有明确或核心的想法。尽管如此,事件总线文档给出的示例是 mitt 和 tiny-emitter。
但是,文档明确表示他们建议按以下顺序处理这些情况:
道具 亲子交流的便捷解决方案。
提供/注入 一种简单的方式让祖先与他们的后代进行交流(尽管很重要,而不是相反)。
Vuex 一种以清晰的方式处理全局状态的方法。重要的是要注意,这不仅仅用于事件或通信 - Vuex 主要是为处理状态而构建的。
本质上,OP 的选择归结为使用事件总线或 Vuex。为了集中事件总线,你可以把它放在 Vuex 中,如果状态也需要全局可用的话。否则,使用对其行为和位置进行严格集中控制的事件总线可能会有所帮助。
扯掉@digout的答案。我在想,如果目的是将数据发送给远祖,那么我们根本不需要 $emit。我为我的边缘案例做了这个,它似乎工作。是的,它可以通过 mixin 来实现,但不是必须的。
/**
* Send some content as a "message" to a named ancestor of the component calling this method.
* This is an edge-case method where you need to send a message many levels above the calling component.
* Your target component must have a receiveFromDescendant(content) method and it decides what
* to do with the content it gets.
* @param {string} name - the name of the Vue component eg name: 'myComponentName'
* @param {object} content - the message content
*/
messageNamedAncestor: function (name, content) {
let vm = this.$parent
let found = false
while (vm && !found) {
if (vm.$vnode.tag.indexOf('-' + name) > -1) {
if (vm.receiveFromDescendant) {
found = true
vm.receiveFromDescendant(content)
} else {
throw new Error(`Found the target component named ${name} but you dont have a receiveFromDescendant method there.`)
}
} else {
vm = vm.$parent
}
}
}
给定一个祖先:
export default {
name: 'myGreatAncestor',
...
methods: {
receiveFromDescendant (content) {
console.log(content)
}
}
}
一个曾孙说
// Tell the ancestor component something important
this.messageNamedAncestor('myGreatAncestor', {
importantInformation: 'Hello from your great descendant'
})
不定期副业成功案例分享
transition
或任何其他包装器组件中,它会损坏,在您的脑海中留下一个大问号。