Emit event from content in slot to parent

不打扰是莪最后的温柔 提交于 2020-01-02 02:18:06

问题


I'm trying to build a flexible carousel control that allows inner content elements to force changing a slide, aswell as the carousel controls itself to change slides

A sample structure in my page looks like

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

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

The template for my carousel is like

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

And script like

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

Accessing the slides etc is no problem, however using $emit will not work and I can't seem to find a simple solution for this problem.

I want to component to be easily reusable without having to use

  • central event bus
  • hardcoded slides within a carousel
  • implement the next slide methods on page level and pass the current index to the control (as I'd have to do this every time I use the carousel)

回答1:


Slots are compiled against the parent component scope, therefore events you emit from the slot will only be received by the component the template belongs to.

If you want interaction between the carousel and slides, you can use a scoped slot instead which allows you to expose data and methods from the carousel to the slot.

Assuming your carousel component has next and close methods:

Carousel template:

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

Carousel example usage:

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



回答2:


It is not possible to listen to events emitted from the slot content by the contained component. In your case, <my-carousel> cannot listen to events next and close. Slot contents are compiled against parent component scope.

As a workaround you can do this:

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

And inside doSomething you can find which button was clicked by using $event.target. Read more about this issue at https://github.com/vuejs/vue/issues/4332 and https://github.com/vuejs/vue/issues/4781

There is one more advanced way of doing this and that is writing custom render function. You wrap click handler passed by a parent into carousel render function and pass a new function to the slot content. But it is something to be done extremely rarely and would consider it close to an anti-pattern.




回答3:


My Solution

Just create an event listener component (e.g. "EventListener") and all it does is render the default slot like so:

EventListener.vue

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

Now use this <event-listener> component and wrap it on your <slot>. Child components inside the slot should emit events to the parent like so: this.$parent.$emit('myevent').

Attach your custom events to the <event-listener @myevent="handleEvent"> component.

Carousel template:

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

Carousel example:

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

Note: The <event-listener> component must only have one child vnode. It cannot be the <slot>, so we just wrapped it on the div instead.




回答4:


I found out this can be done using $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>

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>

child component:

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

However, if possible, used scoped slot as mentionned above.



来源:https://stackoverflow.com/questions/50942544/emit-event-from-content-in-slot-to-parent

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!