ChatGPT解决这个技术问题 Extra ChatGPT

VueJs 2.0 将事件从孙子发送到他的祖父组件

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 两个事件:

一个从孙子到中间组件

然后再次从中间组件发射到祖父母

添加这个中间事件似乎是重复的和不必要的。有没有办法直接发射给我不知道的祖父母?


t
tony19

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}}。

' + '' + '
',data() { return { value: false } },方法:{ toggleValue() { this.value = !this.value } },计算:{ displayValue () { return (this.value ? "ON" : "OFF") } } }) Vue.component('child', { template: '
' + '

我是孩子。我只是提供一些 UI 的包装器。

' + '' + '
' }) Vue .component('grand-child', { template: '
' + '

我是孙子:' + '

',方法:{ emitToggleEvent() { this.$emit('toggle-value') } } }) new Vue({ el: '#app ' }) .child { 填充:10px;边框:1px 实心#ddd;背景:#f0f0f0 }


B
BassMHL

新答案(2018 年 11 月更新)

我发现我们实际上可以通过利用大子组件中的 $parent 属性来做到这一点:

this.$parent.$emit("submit", {somekey: somevalue})

更干净,更简单。


请注意,这仅在关系是孩子 -> 祖父母时才有效。如果子级可以嵌套任意层深,则此方法不起作用。
请参阅我对@Qtax 评论的回答stackoverflow.com/a/55650245/841591
你不希望在你的大项目中发生这种事情。您将“孩子”放在 transition 或任何其他包装器组件中,它会损坏,在您的脑海中留下一个大问号。
@AdamOrlov 我同意,这是不好的做法。请使用 Vuex 商店处理此类事件。
你美丽美丽的人。
B
Bert

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的代码;该代码可以以可重用的方式提取。


这对 IE 有什么不同?不知道vue存在浏览器差异...
@BassemLhm Vue 适合 IE。 IE 的问题不是 Vue,这是一个 DOM 解决方案,您不能在 IE 中执行 new Event()。你必须 document.createEvent()。如果需要,我可以添加 IE 支持。
仅仅为一个简单的案例安装 vuex 是没有意义的。
@AdamOrlov 我同意你的看法。
更简单的解决方案:stackoverflow.com/a/55650245/841591
t
tony19

是的,你是正确的事件只从孩子到父母。他们不会走得更远,例如从孩子到祖父母。

Vue 文档(简要)在 Non Parent-Child Communication 部分解决了这种情况。

一般的想法是在祖父组件中创建一个空的 Vue 组件,该组件通过道具从祖父传递给子孙。然后,祖父母侦听事件,而孙子女在该“事件总线”上发出事件。

一些应用程序使用全局事件总线而不是每个组件的事件总线。使用全局事件总线意味着您将需要具有唯一的事件名称或命名空间,以便事件不会在不同组件之间发生冲突。

以下是 how to implement a simple global event bus 的示例。


d
digout

如果您想灵活地简单地将事件广播给所有父母及其父母,直到根,您可以执行以下操作:

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}

t
tony19

另一种解决方案将在根节点开启/发射:

grand-child 中使用 vm.$root.$emit,然后在祖先(或您想要的任何地方)中使用 vm.$root.$on

更新:有时您希望在某些特定情况下禁用侦听器,请使用 vm.$off(例如:vm.$root.off('event-name') inside生命周期挂钩=beforeDestroy)。

Vue.component('parent', { template: '

我是父级 - {{ action }}
', data(){ return { action: 1, eventEnable: false } }, created: function () { this.addEventListener( ) }, beforeDestroy: function () { this.removeEventListener() }, 方法: { performAction() { this.action += 1 }, toggleEventListener: function () { if (this.eventEnable) { this.removeEventListener() } else { this.addEventListener() } }, addEventListener: function () { this.$root.$on('eventtriggered1', () => { this.performAction() }) this.eventEnable = true }, removeEventListener: function () { this.$root.$off('eventtriggered1') this.eventEnable = false } } }) Vue.component('child', { template: '
我是孩子
',方法:{ doEvent() { //this.$emit('eventtriggered') } } }) Vue.component('grand-child', { template : '
我是孙子
',方法:{ doEvent() { this.$root.$emit('eventtriggered1') } } }) new Vue({ el: '#app' })
<父>


k
kubaklamca

我根据@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 属性 在我的用例中,我不需要一直隧道到根,节省了一些事件的触发时间,也许还有几个宝贵的纳秒时间
r
rogervila

VueJS 2 组件具有包含其父组件的 $parent 属性。

该父组件还包括其自己的 $parent 属性。

然后,访问“祖父母”组件就是访问“父母的父”组件:

this.$parent["$parent"].$emit("myevent", { data: 123 });

无论如何,这有点棘手,我建议使用像 Vuex 这样的全局状态管理器或类似工具,正如其他响应者所说的那样。


对于可以是子或孙子的组件,这不是很好。
正如我所说,这个解决方案是一个技巧。正如其他响应者所说,我建议使用像 Vuex 这样的全局状态管理器或类似工具。
Michael Rush 的回答似乎更适合这些情况。它避免了创建仅用于在祖先链上发回相同消息的方法。
t
tony19

这是我使用 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 中用于将数据从祖父母传递给孙子女,您可以使用提供/注入。但是相反的东西没有类似的东西。 (孙子到祖父)所以每当我必须进行这种通信时,我都会使用事件总线。


N
Nick

引用@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


f
fylzero

我通过创建一个绑定到窗口的类并简化广播/侦听设置以在 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!');
});

我强烈建议不要将随机属性附加到窗口对象,因为它很容易覆盖现有属性或与现有的 3rd 方库发生冲突。相反,任何使用 Vue 来解决这个问题的人都应该使用@roli roli 的答案
我不确定我是否完全理解或同意这种担忧。绑定到原型是一种很好的方法,但是绑定到窗口是很常见的,如果不是更多的话,也可能是一种更标准的处理方式。您为属性命名,因此很容易避免命名冲突。 medium.com/@amitavroy7/… stackoverflow.com/questions/15008464/… 这也是 Jeff Way 在 Laracasts 上使用的建议解决方案。 laracasts.com/series/learn-vue-2-step-by-step/episodes/13
P
Paul F. Wood

从 Vue 3 开始,根事件发生了一些根本性的变化:

$on$off$once 根方法不再存在。在某种程度上可以替换它,因为您可以listen to root events这样做:

createApp(App, {
  // Listen for the 'expand' event
  onExpand() {
    console.log('expand')
  }
})

另一个解决方案是事件总线,但 Vue.js 文档的观点很模糊——从长远来看,它们可能会导致维护问题。您可能会得到一组不断扩展的发射和事件接收器,但对于如何管理它或哪些组件可能在其他地方受到影响没有明确或核心的想法。尽管如此,事件总线文档给出的示例是 mitttiny-emitter

但是,文档明确表示他们建议按以下顺序处理这些情况:

道具 亲子交流的便捷解决方案。

提供/注入 一种简单的方式让祖先与他们的后代进行交流(尽管很重要,而不是相反)。

Vuex 一种以清晰的方式处理全局状态的方法。重要的是要注意,这不仅仅用于事件或通信 - Vuex 主要是为处理状态而构建的。

本质上,OP 的选择归结为使用事件总线或 Vuex。为了集中事件总线,你可以把它放在 Vuex 中,如果状态也需要全局可用的话。否则,使用对其行为和位置进行严格集中控制的事件总线可能会有所帮助。


M
Murrah

扯掉@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'
})

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅