简体   繁体   English

VueJS 2:直接子组件的捕获事件

[英]VueJS 2: Catch event of direct child component

I'm currently trying to get a simple Tabs/Tab component up and running.我目前正在尝试启动并运行一个简单的 Tabs/Tab 组件。 It seems like something in the event handling mechanism has changed, therefore I can't get it to work.似乎事件处理机制中的某些内容已更改,因此我无法使其正常工作。

Current implementation:当前实施:

Tabs.vue选项卡.vue

<template>
    <div class="tabbed-pane">
        <ul class="tab-list">
            <li class="tab" v-for="tab in tabs" @click="activateTab(tab)">{{ tab.header }}</li>
        </ul>
        <slot></slot>
    </div>
</template>
<script>
    import hub from '../eventhub';
    export default {
        props: [],
        data() {
            return  {
                tabs: []
            }
        },
        created() {
            this.$on('tabcreated', this.registerTab)
        },
        methods: {
            registerTab(tab) {
                this.tabs.push(tab);
            },
            activateTab(tab) {

            }
        }
    }
</script>

Tab.vue选项卡.vue

<template>
    <div class="tab-pane" v-show="active">
        <slot></slot>
    </div>
</template>
<script>
    import hub from '../eventhub';
    export default {
        props: {
            'header': String
        },
        data() {
            return {
                active: false
            }
        },
        mounted() {
            this.$emit('tabcreated', this);
        }
    }
</script>

eventhub.js eventhub.js

import Vue from 'vue';

export default new Vue();

View看法

<tabs>
    <tab header="Test">
        First Tab
    </tab>
    <tab header="Test2">
        Second Tab
    </tab>
    <tab header="Test3">
        Third Tab
    </tab>
</tabs>

I've tried the following things:我尝试了以下事情:

  • use a Timeout for the $emit to test if it's a timing issue (it is not)$emit使用 Timeout 来测试它是否是时间问题(不是)
  • use @tabcreated in the root element of the Tabs componentsTabs组件的根元素中使用 @tabcreated
    template模板

It works if...如果...

  • ... I use the suggested "eventhub" feature (replacing this.$on and this.$emit with hub.$on and hub.$emit ) ...我使用建议的“eventhub”功能(将this.$onthis.$emit替换为hub.$onhub.$emit

but this is not suitable for me, as I want to use the Tabs component multiple times on the same page, and doing it with the "eventhub" feature wouldn't allow that.但这不适合我,因为我想在同一页面上多次使用Tabs组件,而使用“eventhub”功能则不允许这样做。

  • ... I use this.$parent.$emit ...我使用this.$parent.$emit

but this just feels weird and wrong.但这只是感觉奇怪和错误。

The documentation states that it IS possible to listen for events triggered by $emit on direct child components https://v2.vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced该文档指出,可以在直接子组件https://v2.vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced上侦听由$emit触发的事件

Does anyone have an Idea?有人有想法吗?

You're right, in vue 2, there is no more $dispatch.没错,在 vue 2 中,不再有 $dispatch。 $emit could work for a single component but it will be scoped to himself ( this ). $emit 可以为单个组件工作,但它的作用域仅限于他自己( this )。 The recommended solution is to use a global event manager, the eventhub .推荐的解决方案是使用全局事件管理器eventhub

the eventhub can be stored in the window object to be used anywhere without import, I like to declare in my main.js file like this: eventthub 可以存储在 window 对象中,无需导入即可在任何地方使用,我喜欢在我的 main.js 文件中声明如下:

window.bus = new Vue()

and then in whatever component:然后在任何组件中:

bus.$emit(...)
bus.$on(...)

It works just the same as this.$root.$emit / this.$root.$on .它的工作原理与this.$root.$emit / this.$root.$on相同。 You said it works when you call this.$parent.$emit , but this code, simulate a scoped emit in the parent component but fired from the child, not good.你说当你调用this.$parent.$emit时它可以工作,但是这段代码模拟了父组件中的作用域发射,但从子组件中触发,不好。

What I understand in your code is that you want to have an array of created tabs, but to do what with them ?我在您的代码中的理解是,您想要创建一组选项卡,但要对它们做什么?

Instead of storing the tab instance in the parent and then activate from the parent, you should think about a more functional way.您应该考虑一种更实用的方式,而不是将选项卡实例存储在父级中然后从父级激活。 The activateTab method should be declared on the tab component and manage the instanciation through the data , something like: activateTab方法应该在 tab 组件上声明并通过data管理实例化,例如:

Tabs.vue选项卡.vue

<template>
    <div class="tabbed-pane">
        <ul class="tab-list">
            <tab v-for="tab in tabs" :header="tab.header"></tab>
        </ul>
    </div>
</template>
<script>
    import hub from '../eventhub';
    import Tab from 'path/to/Tab.vue';
    export default {
        components: [Tab],
        props: [],
        data() {
            return  {
                tabs: ['First Tab', 'Second Tab', 'Third Tab']
            }
        }
    }
</script>

Tab.vue选项卡.vue

<template>
    <div class="tab tab-pane" @click:activeTab()>
        <span v-show="active">Activated</span>
        <span>{{ header }}</span>
    </div>
</template>
<script>
    import hub from '../eventhub';
    export default {
        props: {
            'header': String
        },
        data() {
            return {
                active: false
            }
        },
        methods: {
            activeTab () {
               this.active = true
            }
        }
    }
</script>

This way, your Tab is more independant.这样,您的Tab更加独立。 For parent/child communication keep this in mind :对于父母/孩子的沟通,请记住这一点:

  • parent to child > via props父母到孩子 > 通过道具
  • child to parent > via $emit (global bus)子到父 > 通过 $emit(全局总线)

If you need a more complexe state management you definitely should take a look at vuex .如果您需要更复杂的状态管理,您绝对应该看看vuex


Edit编辑

Tabs.vue选项卡.vue

<template>
    <div class="tabbed-pane">
        <ul class="tab-list">
            <tab v-for="tabData in tabs" :custom="tabData"></tab>
        </ul>
    </div>
</template>
<script>
    import Tab from 'path/to/Tab.vue';
    export default {
        components: [Tab],
        props: [],
        data() {
            return  {
                tabs: [
                  {foo: "foo 1"},
                  {foo: "foo 2"}
                  {foo: "foo 3"}
                ]
            }
        }
    }
</script>

Tab.vue选项卡.vue

<template>
    <div class="tab tab-pane" @click:activeTab()>
        <span v-show="active">Activated</span>
        <span>{{ custom.foo }}</span>
    </div>
</template>
<script>

    export default {
        props: ['custom'],
        data() {
            return {
                active: false
            }
        },
        methods: {
            activeTab () {
               this.active = true
            }
        }
    }
</script>

This is what I don't like about VueJS (2), there is no convenient way of catching events emitted from child components to the parent component.这是我不喜欢 VueJS (2) 的地方,没有方便的方法来捕获从子组件发出的事件到父组件。

Anyways an alternative to this is if you do not want to use the eventhub approach, specially if you are only going to have an event communication between related components ( child and parent ) and not with non-related components, then you can do these steps.无论如何,如果您不想使用 eventthub 方法,特别是如果您只想在相关组件(子组件和父组件)之间而不是与非相关组件之间进行事件通信,那么您可以执行这些步骤.

  1. reference your parent vue component on its data property (very important, you can't just pass this to the child component)在其 data 属性上引用您的父 vue 组件(非常重要,您不能只将传递给子组件)
  2. pass that parent vue component reference as an attribute to the child component ( make sure to bind it)将该父 vue 组件引用作为属性传递给子组件(确保绑定它)
  3. trigger the appropriate event of the parent component inside the child component whenever a desired event is emitted每当发出所需的事件时,在子组件内触发父组件的适当事件

Pseudo code伪代码

// Parent vue component
Vue.component( 'parent_component' , {

   // various codes here ...

   data : {
      parent_component_ref : this // reference to the parent component
   },

   methods : {

      custom_event_cb : function() {
         // custom method to execute when child component emits 'custom_event'
      }

   }

   // various codes here ...

} );

// Parent component template
<div id="parent_component">
   <child_component :parent_component_ref="parent_component_ref"></child_component>
</div>

// Child component
Vue.component( 'child_component' , {

   // various codes here ...

   props : [ 'parent_component_ref' ],

   mounted : function() {

      this.$on( 'custom_event' , this.parent_component_ref.custom_event_cb );


      this.$emit( 'custom_event' );

   },

   // You can also, of course, emit the event on events inside the child component, ex. button click, etc..

} );

Hope this helps anyone.希望这对任何人都有帮助。

Use v-on="$listeners" , which is available since Vue v2.4.0 .使用v-on="$listeners" ,它从 Vue v2.4.0开始可用。 You can then subscribe to any event you want on the parent, see fiddle .然后,您可以订阅父项上的任何事件,请参见fiddle

Credit to BogdanL from Vue Support @ Discord.感谢来自 Vue Support @ Discord 的BogdanL

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM