Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
plehnen opened this issue Mar 16, 2021 · 9 comments
Closed

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

plehnen opened this issue Mar 16, 2021 · 9 comments

Comments

@plehnen
Copy link

plehnen commented Mar 16, 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.

@LinusBorg
Copy link
Member

LinusBorg commented Mar 16, 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>

@plehnen
Copy link
Author

plehnen commented Mar 16, 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)

@LinusBorg
Copy link
Member

LinusBorg commented Mar 16, 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.

@plehnen
Copy link
Author

plehnen commented Mar 16, 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)

@HcySunYang
Copy link
Member

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

@zouyaoji
Copy link

zouyaoji commented Mar 21, 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.

@HcySunYang
Copy link
Member

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

@yyx990803
Copy link
Member

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.

@plehnen
Copy link
Author

plehnen commented May 17, 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?

@github-actions github-actions bot locked and limited conversation to collaborators Oct 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants