将事件从广告位中的内容发送到父级

时间:2018-06-20 07:25:16

标签: vue.js vuejs2 vue-component

我正在尝试构建一个灵活的轮播控件,该控件允许内部内容元素强制更改幻灯片,以及轮播控件本身可以更改幻灯片

页面中的示例结构如下

<my-carousel>
  <div class="slide">
    <button @click="$emit('next')">Next</button>
  </div>

  </div class="slide">
    <button @click="$emit('close')">Close</button>
  </div>
</my-carousel>

我的轮播模板就像

<div class="carousel">
  <div class="slides" ref="slides">
    <slot></slot>
  </div> 
  <footer>
   <!-- other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

和类似的脚本

...
created() {
 this.$on('next', this.next)
}
...

访问幻灯片等没问题,但是使用$ emit无效,我似乎也找不到解决此问题的简单方法。

我希望组件无需使用即可轻松重用

  • 中央事件总线
  • 轮播中的硬编码幻灯片
  • 在页面级别实现下一个幻灯片方法,并将当前索引传递给控件(因为每次使用轮播时我都必须这样做)

8 个答案:

答案 0 :(得分:9)

插槽是根据父组件范围编译的,因此,从插槽发出的事件将仅由模板所属的组件接收。

如果要在转盘和幻灯片之间进行交互,可以使用scoped slot代替,它可以将数据和方法从转盘公开到插槽。

假设轮播组件具有nextclose方法:

轮播模板:

<div class="carousel">
  <div class="slides" ref="slides">
    <slot :next="next" :close="close"></slot>
  </div> 
  <footer>
    <!-- other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

轮播示例用法:

<my-carousel>
  <template slot-scope="scope">
    <div class="slide">
      <button @click="scope.next">Next</button>
    </div>

    </div class="slide">
      <button @click="scope.close">Close</button>
    </div>
  </template>
</my-carousel>

答案 1 :(得分:1)

不可能监听所包含组件从广告位内容发出的事件。就您而言,<my-carousel>无法收听事件nextclose插槽内容是根据父组件范围编译的。

作为解决方法,您可以执行以下操作:

<div class="carousel">
    <!-- Listen to click event here -->
    <div class="slides" @click="doSomething($event)" ref="slides">
        <slot></slot>
    </div> 
    <footer>
        <!-- other carousel controls like arrows, indicators etc go here -->
    </footer>
</div>

doSomething内,您可以使用$event.target查找单击了哪个按钮。在https://github.com/vuejs/vue/issues/4332https://github.com/vuejs/vue/issues/4781

上了解有关此问题的更多信息

有一种更高级的方法可以做到这一点,那就是编写自定义渲染功能。将父级传递的点击处理程序包装到carousel渲染函数中,然后将新函数传递给广告位内容。但这很少做,并且会认为它接近反模式。

答案 2 :(得分:1)

只需将$emit('next')替换为$parent.$emit('next')

答案 3 :(得分:1)

简单方法

export default {
    computed: {
        defaultSlot() {
            return this.$scopedSlots.default();
        }
    },
    methods: {
       this.defaultSlot.forEach(vnode => {
           vnode.componentInstance.$on('someevent', (e) => {
              console.log(e)
           });
                
       });
    }
}

答案 4 :(得分:0)

我的解决方案

只需创建一个事件侦听器组件(例如“ EventListener”),它所做的就是呈现默认的广告位,如下所示:

EventListener.vue

export default {
    name: 'EventListener'
    render() {
        return this.$slots.default;
    }
}

现在使用此<event-listener>组件并将其包装在您的<slot>上。插槽内的子组件应向父对象发出事件,例如:this.$parent.$emit('myevent')

将自定义事件附加到<event-listener @myevent="handleEvent">组件。

轮播模板:

<div class="carousel">
  <event-listener @next="handleNext" @close="handleClose">
     <div class="slides" ref="slides">
       <slot></slot>
     </div> 
  </event-listener>
  <footer>
   <!-- other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

轮播示例:

<my-carousel>
  <div class="slide">
    <button @click="$parent.$emit('next')">Next</button>
  </div>

  </div class="slide">
    <button @click="$parent.$emit('close')">Close</button>
  </div>
</my-carousel>

注意<event-listener>组件只能有一个子vnode。它不能是<slot>,所以我们只是将其包装在div上。

答案 5 :(得分:0)

我发现可以使用$ root来完成。

<h1>Regular html document content</h1>
<parent-component>
  <h2>Some parent html that goes inside the slot</h2>
  <child-component></child-component>
</parent-component>

父组件:

<template>
    <div>
        <slot></slot>
        <h3>extra html that is displayed</h3>
    </div>
</template>
<script>
export default {

    created() {
        this.$root.$on('child-event', this.reactOnChildEvent);
    },

    methods: {
        this.reactOnChildEvent: function(message) {
            console.log(message);
        }
    }
};
</script>

子组件:

<template>
    <div>
      <button @click="$root.$emit('child-event', 'hello world')">
         click here
      </button>
    </div>
</template>

但是,如果可能的话,请使用上面提到的作用域版位。

答案 6 :(得分:0)

选中scoped slot。假设您的轮播组件具有fnNextfnClose方法:

轮播模板:

<div class="carousel">
  <div class="slides" ref="slides">
    <slot name="slide-ctrls" :events="{ fnNext, fnClose }"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

轮播示例用法:

<my-carousel>
  <template slot="slide-ctrls" slot-scope="{ events: { fnNext, fnClose } }">
    <div class="slide">
      <button @click="fnNext">Next</button>
    </div>

    <div class="slide">
      <button @click="fnClose">Close</button>
    </div>
  </template>
</my-carousel>

或者,使用v-slot (更简洁,最新的处理方式):

<my-carousel>
  <template v-slot:slide-ctrls="{ events: { fnNext, fnClose } }">
    <div class="slide">
      <button @click="fnNext">Next</button>
    </div>

    <div class="slide">
      <button @click="fnClose">Close</button>
    </div>
  </template>
</my-carousel>

以防万一,如果您希望看到更扩展的代码形式而不是es6,尽管这似乎有些令人困惑,但它向您显示了事物在何处以及如何传递/使用。

<div class="carousel">
  <div class="slides" ref="slides">
    <slot name="slide-ctrls" :events="{ atClickNext: fnNext, atClickClose: fnClose }"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

轮播示例用法:

<my-carousel>
  <template v-slot:slide-ctrls="{ events: { atClickNext: handleClickNext, atClickClose: handleClickClose } }">
    <div class="slide">
      <button @click="handleClickNext">Next</button>
    </div>

    <div class="slide">
      <button @click="handleClickClose">Close</button>
    </div>
  </template>
</my-carousel>

答案 7 :(得分:0)

我知道这是一篇较旧的帖子,但是,它在 Google 上的排名很好 - 所以我想我会详细说明我发现的解决方法。如果有更好的方法来实现这一点,我欢迎反馈。

为了解释解决方案,我将使用日历示例...


这是我的场景

通用日历 -> 日历年 -> 日历月 -> 日历日

在日历日内,有一个插槽(日历日单元格)允许父母呈现当天的自定义视图。此槽沿行向上传递到父“日历”组件。

在我的场景中,我有一个使用“日历”的可用性日历,并覆盖了组件可用性日历日中传递的日历日单元格。

availability-calendar-day 发出“available-date-selected”,在这种情况下,“calendar”不需要知道这个事件。在堆栈中,只有可用性日历组件需要使用它。

模板:

<template> <!-- availability-calendar -->
  <calendar> 
    <template #calendar-day-cell>
      <availability-calendar-day @available-date-selected="dateSelected">

脚本:

{
  name: 'availability-calendar',
  methods:
  {
    dateSelected(date) {
      // ...
    }

问题

来自可用性日历日的发射未达到可用性日历。这是因为它没有复制到“日历”堆栈中。 IE。发射仅发射到 'calendar-day' 组件(定义插槽)。

解决方案

这不是一个纯粹的解决方案,但是,它确实对我有用,我欢迎任何其他关于解决方法的评论。

鉴于在插槽模板中定义的组件接受来自声明组件的 props,我完全绕过了事件过程并将所需的方法作为函数传递到组件中。

使用前面的示例,模板现在看起来像这样:

<template> <!-- availability-calendar -->
  <calendar> 
    <template #calendar-day-cell>
      <availability-calendar-day :dateSelectedHandler="dateSelected">

然后,在 'availability-calendar-day' 内,方法从 this.$emit('available-date-selected') 更改为 this.dateSelectedHandler() em>。为了在 Typescript 编译组件中支持这一点,prop 被输入为 Function