Subscribe on changes!

Recursive compontent that emits cannot be created

avatar
Mar 4th 2021

Version

3.0.7

Reproduction link

https://codesandbox.io/s/recursive-emit-bug-10073

Steps to reproduce

See codesandbox for illustration of the problem

  • Create a recursive component that has an 'emits: ["event"]'
  • Emit a custom event $emit("event", depth) from recursive component
  • Add v-bind="$attrs" to every recursive inclusion from recursive component to enable emitting from any level
  • Finally use the recursive component with an @event="..." handler

What is expected?

The @event="..." is called for any "depth"

What is actually happening?

Only the first (top level) of the recursive component is handled


I spent some hours debugging this issue. According to documents we should add emits: ["<name>"] to our components. However, by doing so, we capture the event listener and effectively remove it from $attrs at the first "level".

The system behaves according to specifications, but it feels wrong to have to leave out emits: ["<name>"] when you build an emitting recursive component. At least for me it was very counter-intuitive.

Sadly I do not have a good solution as I understand why you capture any defined props and emits from the $attrs. However, maybe I am just not doing it right, would love to hear how we should create an emitting recursive component.

P.S. I found this problem when I was creating a file explorer component using a single recursive component.

avatar
Mar 5th 2021

Thanks for the information! I thought that solution (re-emitting event @event="(depth) => $emit('event', depth)") was suboptimal because of call stack depth and performance reasons. For posterity: I was partially wrong on that front https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/

TL;DR; If you use ES6 and have strict mode on, Tail Call Optimization is used. Essentially it changes a returning function call at the end of a function into a jump. However, the system still emits (potentially many times) events decreasing performance.

avatar
Mar 5th 2021

Yeah, I did also aware of the problem. I think I should reopen this issue for further discussion.

avatar
Oct 6th 2022

Hi, so as summary there is two way for recursive nesting component:

  1. Emits from Template and not have defineEmits on the script
  • Template of recursive nested component
    • Where the event is generated:
      • @click.stop="$emit('start', element)"
    • Where the children that is nested:
      • v-bind="$attrs"
  • Template of grand parent
    • @start='dragStart'
  1. Emits normally from script but have a recieve/sender of the event in each node
  • Template of recursive nested component
    • Where the event is generated:
      • @click.stop="handleClick($event, element.node)"
    • Where the children that is nested
      • @start="(node) => $emit('start', node)"
  • Script
    • const emit = defineEmits(['start'])
    • function handleClick(event:any,node:Node) { emit('start', {event, node}); }
  • Template of grand parent
    • @start='dragStart'

is this correct? can you give me a suggest of which is the best way?