-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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 #5
Comments
Great overview!
I think that makes sense. Then users never have to refactor |
I like all of these changes. I felt |
@yyx990803 Quick question: why do we need |
Oh, that's only needed if you have your own data that you want to merge into. |
I have some queries for this:
Here is an example component in React:
In Vue 2.x a similar component may be (typing retracted):
For React:
All of the cases for React, in my opinion, hinder A11Y as you can not practically just transfer global attributes to an element. For Vue: All of the attributes are transferred, and class names styles are merged which is great for accessibility. However there is one issue vuejs/vue#7557 which means everything is transferred, which is not ideal as every prop is directly transferred, and normally there is the mapping of props to attributes. The solution would be explicit like React or manually merge At attest we have a little snippet that strips all non global attributes from the attrs object, which means that all global attributes such as
What this means is that we need to be careful with similarly named props as these will transfer, however, this does mean we get This would be awesome if we can come to a solution that improves upon Reacts issues with functional components, so that we can have easy merging of class names and attribute tranfer. Any suggestions? |
@blake-newman not entirely sure if I understand your point... but is the concern about spreading
|
No that is fine, the problem is when you have functional components and no defined props. This is common when using this definition: https://github.com/nickmessing/babel-plugin-jsx-vue-functional Attributes will all flood to the dom, and with more complex functional component this is an issue.
will render every attribute with mergeds styles and class names The merging is great as it is by far less hindering to passing down aria roles - where is React you would need to explicitly pass through individual values to the element for those attributes to render So with the Vue way, we will render everything out, but in React this does not happen. Both have their benefits and the above code snippet I shared partially resolves this issue by allowing only global attributes to be passed through to the element. |
Right, but I think in those cases you are probably better off declaring the props explicitly. |
In that case I think we should mimic React keeping class name and style merging but not passing through attributes on spread to element nodes. This means that you get best of both worlds. Easy merging of classes and styles and explicit passing of props. |
@blake-newman the problem with implicit class and style merging is that it does not always work - e.g. when the component returns a fragment. A simple refactoring can accidentally cause things to break. |
Yes i agree, that is why you must still explicitly ask for This is what I propose which follows React following 2.x API as i'm unsure on the structure for 3.x: This should be consistent for component fallthrough and element fallthrough Case 1JS: (h, { data }) => cloneVNode(h('div', { class: { 'foo' } }), data) JSX: ({ data }) => <div class="foo" {...data}> Use: <Example data-test="test" class="bar" /> Renders: <div data-test="test" class="foo bar"></div> Outcome: Attribute merging with full fallthrough if no attributes/props specified Case 2JS: (h, { data }) => cloneVNode(h('div', { class: { 'foo' }, attrs: { 'data-test': data.props['data-test'] } }), data) JSX: ({ data, props, attrs }) => <div class="foo" data-test={props.dataTest || attrs['data-test']} {...data}> Use: <Example data-test="test" myProp="test" class="bar" /> Render: <div class="foo bar" data-test="test"></div> Outcome: Attribute merging, no fallthrough of additional props/attribute, must be explicit This means you need to be more explicit but this prevents cases where props are rendered as attributes which is a big issue for our application. In hindsight our global attribute fallthrough is incorrect and i much prefer this behaviour as React does |
@blake-newman I'm not sure if I can understand your proposal. A few notes regarding v3: Because VNode data is now flat, there's no longer the difference between
So Case 1 would look like this (note v3 also drops the leading const Foo = props => cloneVNode(h('div', { class: 'foo' }), props) If you want to use some of the props and only spread the rest: const Foo =({ foo, ...attrs }) => cloneVNode(h('div', { class: 'foo', id: foo }), attrs) Your Case 2 seems very convoluted and I can't really understand what you are trying to do. Maybe you should take a step back and explain "cases where props are rendered as attributes". But in v3, if the component has declared props, you'd get a clear separation between |
Okay, this makes a lot more sense, sorry for the confusion. My understanding was based on a 2.x issue where their vNode data was not flat, which would mean all information even if extracted would be transferred as attributes if you did not empty Now that it is flat, properties won't be transferred when extracting which will mitigate this issue. |
Closed in favor of #27 |
Current proposal:
inhertiAttrs
optionnativeOn
$attrs
on desired target element<div v-bind="$attrs">
return cloneVNode(h('div'), this.$attrs)
$attrs
will be empty and everything will be in$props
, and it should just spread / pass on$props
instead.$attrs
will just point to$props
which includes everything passed to the component.Rationale
The fallthrough behavior has already been inconsistent between stateful components and functional components in 2.x. With the introduction of fragments, 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. By making this an explicit decision of component authors, whether a component accepts additional attributes becomes part of the component's API contract, overall resulting in more consistency.
The combination of
inheritAttrs
,nativeOn
,$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".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, but can be easily worked around with by wrapping the component in a wrapper element.
For accessibility reasons, it should be a best practice for components that are shipped as libraries to always spread
$attrs
. 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.Side Benefit
Not spreading
$attrs
actually improves performance.The text was updated successfully, but these errors were encountered: