【问题标题】:VueJS 2: Catch event of direct child componentVueJS 2:直接子组件的捕获事件
【发布时间】:2017-03-28 01:07:54
【问题描述】:

我目前正在尝试启动并运行一个简单的 Tabs/Tab 组件。 似乎事件处理机制中的某些内容发生了变化,因此我无法让它工作。

当前实现:

Tabs.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

<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

import Vue from 'vue';

export default new Vue();

查看

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

我尝试了以下方法:

  • $emit 使用超时来测试是否是时间问题(它是 不是)
  • Tabs 组件的根元素中使用@tabcreated
    模板

如果......它会起作用

  • ... 我使用建议的“eventhub”功能(替换 this.$onthis.$emithub.$onhub.$emit)

但这不适合我,因为我想在同一页面上多次使用Tabs 组件,而使用“eventhub”功能则不允许这样做。

  • ...我用this.$parent.$emit

但这只是感觉很奇怪和错误。

文档指出可以在直接子组件上侦听由$emit 触发的事件 https://vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced

有人有想法吗?

【问题讨论】:

    标签: javascript events event-handling vue-component vue.js


    【解决方案1】:

    你说得对,在 vue 2 中,没有更多的 $dispatch。 $emit 可以用于单个组件,但它的作用域仅限于他自己 (this)。推荐的解决方案是使用全局事件管理器eventhub

    eventthub 可以存储在 window 对象中,无需导入即可在任何地方使用,我喜欢在我的 main.js 文件中声明如下:

    window.bus = new Vue()
    

    然后在任何组件中:

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

    它的工作原理与this.$root.$emit / this.$root.$on 相同。您说调用this.$parent.$emit 时它可以工作,但是这段代码模拟了父组件中的作用域发射,但从子组件中触发,不好。

    我在您的代码中的理解是,您想要创建一组标签,但要对它们做什么?

    您应该考虑一种更实用的方式,而不是将选项卡实例存储在父级中,然后从父级激活。 activateTab 方法应该在 tab 组件上声明并通过 data 管理实例化,例如:

    标签.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>
    

    标签.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>
    

    这样,您的Tab 更加独立。对于父母/孩子的沟通,请记住这一点:

    • 父母对孩子>通过道具
    • 子到父 > 通过 $emit(全局总线)

    如果您需要更复杂的状态管理,您绝对应该看看vuex


    编辑

    标签.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>
    

    标签.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>
    

    【讨论】:

    • 好吧,我需要 Tab 不仅仅包含一个 Header ;) 但是好吧,我会坚持使用事件总线并命名选项卡
    • 因此您可以将一个对象或多个属性传递给道具。例如:Tabs.vue ... data() { return { tabs: [{foo: "foo1", bar:" bar1"}, {foo: "foo2", bar:"bar2"}] } } 而在 Tab.vue 中
    【解决方案2】:

    这是我不喜欢 VueJS 的地方(2),没有方便的方法来捕捉从子组件发出到父组件的事件。

    无论如何,如果您不想使用 eventthub 方法,特别是如果您只想在相关组件(子组件和父组件)之间而不是与非相关组件之间进行事件通信,那么您可以执行这些步骤。

    1. 在其 data 属性上引用您的父 vue 组件(非常重要,您不能只将 this 传递给子组件)
    2. 将父 vue 组件引用作为属性传递给子组件(确保绑定它)
    3. 只要发出所需的事件,就在子组件内触发父组件的相应事件

    伪代码

    // 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..
    
    } );
    

    希望这对任何人都有帮助。

    【讨论】:

    • 我真的更喜欢事件总线而不是这个解决方案,因为它更干净。这是一个你可以考虑的 hack,但根本不是 Vue 的预期用途
    • @FabianBettag 正如我上面所说的,当您不想要事件总线样式时,这是一种替代方案,特别是如果通信仅在父组件和子组件之间进行。让您免于创建单独的 vue 实例。
    【解决方案3】:

    使用 v-on="$listeners",自 Vue v2.4.0 起可用。然后,您可以订阅任何您想要的父事件,请参阅fiddle

    感谢来自 Vue Support @ Discord 的 BogdanL

    【讨论】:

      猜你喜欢
      • 2017-07-17
      • 2017-07-09
      • 2017-12-23
      • 1970-01-01
      • 2018-07-03
      • 2019-05-22
      • 2021-07-18
      • 2018-05-11
      • 1970-01-01
      相关资源
      最近更新 更多