Subscribe on changes!

onX function removed from $attrs as soon as documented in emits prop

avatar
Mar 16th 2021

Version

3.0.7

Reproduction link

https://codesandbox.io/s/onx-function-removed-from-attrs-as-soon-as-documented-in-emits-prop-n9ytr?file=/src/components/ParentComp.vue

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.

avatar
Mar 16th 2021

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>
avatar
Mar 16th 2021

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)

avatar
Mar 16th 2021

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 in props:, it will not be part of $attrs
  • if click is defined in emits, 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.

avatar
Mar 16th 2021

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)

avatar
Mar 17th 2021

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

avatar
Mar 21st 2021

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.

avatar
Mar 21st 2021

@zouyaoji Sure, but it is not for the users, we are talking about the user interface

avatar
Apr 1st 2021

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.

avatar
May 17th 2021

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.

so... is there already an own thread for this?