onX function removed from $attrs as soon as documented in emits prop
Version
3.0.7
Reproduction link
Steps to reproduce
Please see minimal reproduction. When emits
prop is set with "test1" the onTest1
function is removed from $attrs
, so it is not possible to pass it down to a child-component, or not possible to properly use the recommended documentation what is emitted within the scope of the current component.
What is expected?
shouldn't delete emit function (onTest1) from $attrs
(especially when using inheritAttrs: false)
What is actually happening?
emit is deleted for children component and not possible to pass it down.
Yeah that's by design. Like props, emits aren't part of attrs
. It contains only attributes that are not part of the components props or emits.
I see the use case though - we don't have an event counterpart to $props
.
You can make it work like this, adding it to props:
instead of emits:
:
<template>
<h2>From children:</h2>
<EmittingChildComp v-bind="{ ...$attrs, onTest1 }" />
<hr />
<h2>Inline:</h2>
<button @click="$emit('test1')">emit test1</button>
<button @click="$emit('test2')">emit test2 (will output warning)</button>
</template>
<script>
import EmittingChildComp from "./EmittingChildComp.vue";
export default {
name: "ParentComp",
inheritAttrs: false,
components: {
EmittingChildComp,
},
props: ['onTest1'],
//emits: ["test1" /*, 'test2'*/], // This is the issue: As soon as I expose that "test1" can be emitted, it is missing in $attrs!
};
</script>
But according to the docs (https://v3.vuejs.org/guide/migration/emits-option.html#_3-x-behavior) "emits will now be included in the component's $attrs
, which by default will be bound to the component's root node".
For me it is difficult to understand why documenting emits will affect what's inside $attrs
. And as seen in the codesandbox, either I have to live with the warning (if I actually need to emit from the "parent" too), or have to violate the recommendation of documenting the emits properly.
I guess I can live with the props: ['onTest1'],
hack, but it should probably be explained in the docs. And I actually would prefer a more intuitive behaviour without having emits to affect whats in $attrs
.
But maybe there is a good reason, and I just don't understand why it is wanted to have this removed from $attrs
. I simple don't see the benefit.
(And it took me a while to figure out why my code wasn't working as expected. So at least this should be faced: Either by changing its logic, or by making the documentation more clear about it)
But according to the docs (v3.vuejs.org/guide/migration/emits-option.html#_3-x-behavior) "emits will now be included in the component's $attrs, which by default will be bound to the component's root node".
You seem to have misread (emphasis mine):
This is especially important because of the removal of the .native modifier. Any listeners for events that aren't declared with
emits
will now be included in the component's $attrs, which by default will be bound to the component's root node.
For me it is difficult to understand why documenting emits will affect what's inside $attrs.
<child-component id="myId" @click="someHandler" />
- if
id
is defined inprops:
, it will not be part of$attrs
- if
click
is defined inemits
, it will not be part of$attrs
The reasoning is that $attrs
should only contain attributes that are not explicitly handled by the component.
- props are handled separately with
$props
- emits are handled separately through
emit()
It's not common that a component would want to treat a custom event both as its own event and have it fall through to a child like you do - usually, only unhandled attributes/events are being passed down.
Your case is valid of course, but not common. If we didn't remove props and events from $attrs
, then developers would have to manually filter them in the common scenario:
<label>
<input v-bind="filteredAttrs">
</label>
computed: {
filteredAttrs() {
return /* this.$attrs filtered to not contain stuff declared in props & emits */
}
}
All that being said, we should improve documentation about this, and/or concider a better way to access the listeners of declared events than forcing devs to declare them as on*
props.
Got it. Thanks a lot for explaining!
In my "real" situation I only pass it down to the child. So it is actually not a big deal for me.
I just assumed it would be best practice to add it to the emits
, as the docs says: "It is highly recommended that you document all of the events emitted by each of your components using emits." (and practically this component is the one who will emit it. at least it will look like this from outside)
And after I did so, (and some other changes), suddenly it didn't work anymore, which was very confusing and difficult to find.
It think it would be really nice to have an $rawAttrs
, as in my code I already have to write v-bind="{ ...$props, ...$attrs }"
to really pass everything down. And this should contain the emits as well. (so this would actually solve two problems really nicely, as internally vue should have access to the rawAttrs anyway)
Currently, if an event is declared as emits
, then we have no way to access its listener, neither through $props
nor $attrs
. Most of the time it’s okay, but it’s a little uncomfortable in recursive components, see https://github.com/vuejs/vue-next/issues/3361
Currently, if an event is declared as
emits
, then we have no way to access its listener, neither through$props
nor$attrs
. Most of the time it’s okay, but it’s a little uncomfortable in recursive components, see #3361
Here is a way to get the listener, which can be obtained through instance.vnode.props
.
Closing as the original behavior being raised is by design. Whether we need a way to expose declared emits
listeners should have its own thread.