Recursive compontent that emits cannot be created
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.
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.
Hi, so as summary there is two way for recursive nesting component:
- 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"
- Where the event is generated:
- Template of grand parent
- @start='dragStart'
- 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)"
- Where the event is generated:
- 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?