-
-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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
Attribute Fallthrough Behavior #27
Comments
|
It clones the VNode :) The 2nd argument to
What about a new option called export default {
props: ['foo'],
emits: ['click']
} I believe we've had similar feature requests in the past, but we did not implement it because we felt it didn't provide enough value. Now this would serve a few useful purposes:
|
Being able to declare slots in the same way could similarly help with automatic doc generation and autocompletion. But on its own perhaps that's not enough added value to be considered? |
|
There's also the topic of custom directives used on components. those also don't work anymore (or atleast don't make sense anymore), since we now have Fragements support and there's no guaranteed root element. Were should we document this? Here? or in #29 ? |
Aren't directives going to take VNodes now instead of an element? |
If so I missed the memo ... :-P |
Maybe I misinterpreted that |
Whereas I agree with that, isn't automatic inherit still the most common case (except for multiple children). When developing components in apps, I won't add the How are |
That's a good point. Yeah I don't think it makes sense anymore. The directive API is indeed going to change so that will be another RFC.
I think explicit is better than implicit here. It's simply impossible to keep it consistent with the implicit behavior. When fragments become available, it is very inconsistent if the user have to add
|
I also prefer explicit. I think the implicit behaviour here is something a lot of users like, so I want to be able to explain correctly.
I meant that if |
That's what I mentioned in the "Unresolved questions".
Yes. This is not really a breaking change since attributes and props need to be nested in 2.x. You can still declare props named |
Published: vuejs/rfcs#26 |
Draft based on #5
Summary
Disable implicit attribute fall-through to child component root element
Remove
inheritAttrs
optionBasic example
To replicate 2.x behavior in templates:
In render function:
Motivation
In 2.x, the current attribute fallthrough behavior is quite implicit:
class
andstyle
used on a child component are implicitly applied to the component's root element. It is also automatically merged withclass
andstyle
bindings on that element in the child component template.However, this behavior is not consistent in functional components because functional components may return multiple root nodes.
With 3.0 supporting fragments and therefore multiple root nodes for all components, this becomes even more problematic. The implicit behavior can suddenly fail when the child component changes from single-root to multi-root.
attributes passed to a component that are not declared by the component as props are also implicitly applied to the component root element.
Again, in functional components this needs explicit application, and would be inconsistent for 3.0 components with multiple root nodes.
this.$attrs
only contains attributes, but excludesclass
andstyle
;v-on
listeners are contained in a separatethis.$listeners
object. There is also the.native
modifier. The combination ofinheritAttrs
,.native
,$attrs
and$listeners
makes props passing in higher-order components unnecessarily complex. The new behavior makes it much more straightforward: spreading $attrs means "pass everything that I don't care about down to this element/component".class
andstyle
are always automatically merged, and are not affected byinheritAttrs
.The fallthrough behavior has already been inconsistent between stateful components and functional components in 2.x. With the introduction of fragments (the ability for a component to have multiple root nodes) in 3.0, the fallthrough behavior becomes even more unreliable for component consumers. The implicit behavior is convenient in cases where it works, but can be confusing in cases where it doesn't.
In 3.0, we are planning to make attribute fallthrough an explicit decision of component authors. Whether a component accepts additional attributes becomes part of the component's API contract. We believe overall this should result in a simpler, more explicit and more consistent API.
Detailed design
inheritAttrs
option will be removed..native
modifier will be removed.Non-prop attributes no longer automatically fallthrough to the root element of the child component (including
class
andstyle
). This is the same for both stateful and functional components.This means that with the following usage:
Both
bar="2"
ANDclass="bar"
on<child>
will be ignored.this.$attrs
now contains everything passed to the component except those that are declared as props or custom events. This includesclass
,style
,v-on
listeners (asonXXX
properties). The object will be flat (no nesting) - this is possible thanks to the new flat VNode data structure (discussed in Render Function API Change).To explicitly inherit additional attributes passed by the parent, the child component should apply it with
v-bind
:This also applies when the child component needs to apply
$attrs
to a non-root element, or has multiple root nodes:In render functions, if simple overwrite is acceptable,
$attrs
can be merged using object spread. But in most cases, special handling is required (e.g. forclass
,style
andonXXX
listeners). Therefore acloneVNode
helper will be provided. It handles the proper merging of VNode data:The 2nd argument to
cloneVNode
is optional. It means "clone the vnode and add these additional props". ThecloneVNode
helper serves two purposes:class
,style
and event listenersInside render functions, the user also has the full flexibility to pluck / omit any props from
$attrs
using 3rd party helpers, e.g. lodash.Removing Unwanted Listeners
With flat VNode data and the removal of
.native
modifier, all listeners are passed down to the child component asonXXX
functions:compiles to:
When spreading
$attrs
withv-bind
, all parent listeners are applied to the target element as native DOM listeners. The problem is that these same listeners can also be triggered by custom events - in the above example, both a native click event and a custom one emitted bythis.$emit('click')
in the child will trigger the parent'sfoo
handler. This may lead to unwanted behavior.Props do not suffer from this problem because declared props are removed from
$attrs
. Therefore we should have a similar way to "declare" emitted events from a component. There is currently an open RFC for it by @niko278.Event listeners for explicitly declared events will be removed from
$attrs
and can only be triggered by custom events emitted by the component viathis.$emit
.Drawbacks
Fallthrough behavior is now disabled by default and is controlled by the component author. If the component is intentionally "closed" there's no way for the consumer to change that. This may cause some inconvenience for users accustomed to the old behavior, especially when using
class
andstyle
for styling purposes, but it is the more "correct" behavior when it comes to component responsibilities and boundaries. Styling use cases can be easily worked around with by wrapping the component in a wrapper element. In fact, this should be the best practice in 3.0 because the child component may or may not have multiple root nodes.For accessibility reasons, it should be a best practice for components that are shipped as libraries to always spread
$attrs
so that anyaria-x
attributes can fallthrough. However this is a straightforward / mechanical code change, and is more of an educational issue. We could make it common knowledge by emphasizing this in all our information channels.Alternatives
N/A
Adoption strategy
Documentation
This RFC discusses the problem by starting with the 2.x implementation details with a lot of history baggage so it can seem a bit complex. However if we were to document the behavior for a new user, the concept is much simpler in comparison:
For a component without explicit props and events declarations, everything passed to it from the parent ends up in
$attrs
.If a component declares explicit props, they are removed from
$attrs
.If a component declares explicit events, corresponding
onXXX
listeners are removed from$attrs
.$attrs
essentially means extraneous attributes,, or "any attributes passed to the component that hasn't been explicitly handled by the component".Migration
This will be one of the changes that will have a bigger impact on existing code and would likely require manual migration.
We will provide a warning when a component has unused extraneous attributes (i.e. non-empty
$attrs
that is never used during render).For application code that adds
class
/style
to child components for styling purposes: the child component should be wrapped with a wrapper element.For higher-order components or reusable components that allow the consumer to apply arbitrary attributes / listeners to an inner element (e.g. custom form components that wrap
<input>
):Declare props and events that are consumed by the HOC itself (thus removing them from
$attrs
)Refactor the component and explicitly add
v-bind="$attrs"
to the target inner component or element. For render functions, apply$attrs
with thecloneVNode
helper.If a component is already using
inheritAttrs: false
, the migration should be relatively straightforward.We will need more dogfooding (migrating actual apps to 3.0) to provide more detailed migration guidance for this one, since the migration cost heavily depends on usage.
The text was updated successfully, but these errors were encountered: