-
-
Notifications
You must be signed in to change notification settings - Fork 4.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
Distinguish between properties and attributes on custom elements #12624
Comments
I actually love this a lot. Simplifies a ton of things along the way too. |
And I assume to pass a string as a property the syntax would be this? (if I don't have it in a variable) <foo-bar someprop={"my string here"}></foo-bar> |
My only qualm with this is that it seems hard to teach and unintuitive. Why does wrapping something in quotes mean that it's an attribute? |
Because HTML attributes use quotes |
But in the case To me, It's kind of like if |
|
What would the recommended way to do string interpolation for a property? |
Exactly, just like in normal JavaScript: props = {
prop: `${foo} a b`
}; |
Yes I love this ❤️ |
What about spreading properties? Does this always count as setting them as properties? What about regular elements? I've seen (very few, rare) cases where people wanted things explicitly as an attribute or property. As for timing: Since this is an uncommon case and can easily solved through e.g. an action, I'm in favor of taking our time with it and don't rush this into 5.0 |
How about boolean attributes? REPL <input type="text" disabled={disabled}>
<!-- or -->
<input type="text" {disabled}> Also, I believe it will be very easy to confuse the following, especially for new users: <my-element a="{variable}" b={"text"} /> If we are going to make a breaking change, why not just force props to use a prefix, like <my-element a={variable} :b="text" /> ...and the same rules we knew so far about Side now: I totally agree with |
I've been meaning to file an issue for this for ages, but never got around (totally not me bumping into this last year and promising to file an issue for it... 😅 )
there's actually a syntax in vue for explicitly attributes as well: I think
Since this is an edge case, trading a less elegant/simple API for explicitness / programmatic control could be worth the tradeoff? |
There's basically three options:
Given that a custom element has a finite number of props and a potentially infinite number of attributes, I would argue that (1) doesn't make sense — if you want to carve the props out of I think (2) is the best option — it's consistent with how normal elements are handled, and easier to explain. We could augment it with a 'did you mean to do
For consistency I think we'd want to treat This does create an inconsistency with
Thanks, I hate it 🤪
I do think that if we started to nudge people towards a similar distinction for normal elements (by warning on quoted boolean properties like |
Just checked what we currently do... <a-b x={1} y="2"></a-b>
<c-d {...stuff}></c-d> ...and we use export default function App($$anchor) {
var fragment = root();
var a_b = $.first_child(fragment);
- $.set_custom_element_data(a_b, "x", 1);
- $.set_custom_element_data(a_b, "y", "2");
+ $.set_custom_element_property(a_b, "x", 1);
+ $.set_custom_element_attribute(a_b, "y", "2");
var c_d = $.sibling(a_b, 2);
let attributes;
$.template_effect(() => attributes = $.set_attributes(c_d, attributes, { ...stuff }, undefined, true));
$.append($$anchor, fragment);
} ...along with an implementation of |
Yes and no - This distinction between custom elements and regular elements and how string attributes are set will require a lot of explanation, and I'm not sure the nuance is easily grasped. If anything, this makes me lean towards aligning it and meaning "string attributes will always be set through |
That strikes me as very odd, to be honest. It's a big departure from how directives normally work... <!-- one of these things is not like the others! -->
<CustomElementWrapper on:eventname attr:foo prop:bar /> ...and it gets particularly weird if you want to reference the value inside the wrapper for whatever reason: let props = $props();
// i can easily imagine people being confused as to why `props.foo` and `props.bar` are undefined
let foo = props['attr:foo'];
let bar = props['prop:foo']; |
Opened #12981 so we can try this out |
You're right, spreading is probably best handled through a option 2 then, but my point about readability stands |
Moving from |
There are three possibilities:
So in the worst case — 3 — you're guided towards the correct outcome and you're learning something about how the element is implemented. To me the more interesting case is 2. Maybe you need special-img:not([src]) {
outline: 5px solid red;
} ...or because you need it for With this proposal, it's trivial. As far as I'm concerned it's all upside. |
Sounds good. |
That's a great point, I didn't really think about SSR in the PR but it's another point in this proposal's favour. At the moment we have to assume everything is an attribute, even if that results in |
What do we do about dash-case? It's somewhat common to use dash-case on attributes and convert them into camelCase on the corresponding attribute. <c-e a-property={true}></c-e> You likely don't want this as |
We decided to punt on this until 6.0 |
@trueadm said that React had a similar syntax to distinguish between properties and attributes way back, and it was very confusing to people when to use which. This may happen here aswell, especially given that the quotes have slightly different meanings on other kinds of elements and components. |
Alloooo 👋 |
Whether the quotes are too magic is a matter that we'll have to sort out at some point. But, to be honest, interactions with Pug aren't really going to enter into the decision. |
Describe the problem
One of the many reasons custom elements are a pain in the neck is that it's often rather ambiguous what's supposed to be an attribute and what's supposed to be a property. Some custom elements 'reflect' attributes back to properties and vice versa, but there's no universally agreed upon convention. If you're doing low-level DOM manipulation you can decide whether you want to do
element.setAttribute(name, value)
orelement[name] = value
, but everyone stopped writing code like that a decade ago.It's a messy, flawed design, and different frameworks deal with it in different ways. Preact and Svelte both treat something as a property if
prop in node
, and an attribute otherwise. Vue does the same thing but additionally gives you a way to force something to be a prop (I don't think you can force something to be an attribute) with some interesting syntax choices:Lit leans even harder into syntax, with
?
and.
and@
prefixes for boolean attributes, properties and event handlers respectively:I think the explicit control is a good thing, but I think we can provide it more naturally.
Describe the proposed solution
#7925 and #12479 point the way. We're moving to a world in which quoted props on components are stringified:
(The reason the quotes are ignored today is historical: in the distant past before Svelte editor extensions, when components were typically written in
.html
files, it avoided crazypants syntax highlighting.)What if we did something similar for custom elements?
In other words if something is quoted, it's an attribute, otherwise it's a prop. We might need to change some Prettier defaults, but other than that this should be a relatively straightforward change. (I don't see any need to distinguish between 'boolean attributes' and properties like Lit does — they're all just properties.)
It is, of course, a breaking change. We would need to decide whether to do this in 5.0, or have a bunch of warnings and change it in 6.0. Ordinarily the conservative thing to do would be to delay the breaking change, but since it's not currently possible to differentiate properties and attributes I think there's a reasonable case for doing it sooner. As such I've given it a 5.0 milestone for now.
Importance
nice to have
The text was updated successfully, but these errors were encountered: