-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Explicit bindings with $props.bindable()
#10768
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
Comments
I love this! I've been hoping for some kind of explicit declaration of binds ever since I read this proposal a while back: #9997 With that said, to bikeshed on the name for a bit, similar to #10334, Perhaps some better alternatives could be |
lol what are the odds, prototype methods strike again.
|
$props.bind()
$props.bindable()
Ha, whoops! Agree — I've updated the issue to |
Bindable also is more correct, since it doesn't necessarily mean it is bound. I'm curious how this will interact with #9764. If the issue was with complications around defaults for bound values, does that mean defaults for |
This doesn't change the rules around defaults. You can still use them for normal properties. It only errors when you have a fallback value defined but then do |
Would a naming scheme using |
If you're only interested in the value changing, use an event callback. Using |
This assumes you are the author the component, for third party components, being able to observe prop changes is really nice for a variety of reasons. If an author doesn't make their prop bindable then you have to make a PR or you are done. |
Can you give a concrete example? I've never seen this in the wild before |
Very glad to see this happening! |
since we're doing this , can we do
in the doc , we can just say readonly and bindable are more recommended ... i would love to keep $props bindable, this would make not change any exisitng code |
We definitely don't want to optimize for Svelte 5 early adopters. It's much better to make current Svelte 5 users deal with a breaking change than it is to ship an API with 5.0 that we wish worked differently. |
sure more work to do :( , but i still think if u really dont like what i propose then plz plz add some error message so i can migrate $props one by one |
As I said in the opening comment, using a binding with a non-bindable prop will cause both a type error and a runtime error |
Hm, I was confused by some behavior in #10779 (thanks, @Conduitry for explaining it) while thinking about this. Your example uses strings, but how would this work with objects? Something like this: repl. As far as I understand, |
During implementing this I came across some edge cases which made me question whether or not this is the best design for declaring properties as bindable. It basically all comes down to the fact that it's two runes instead of one. Initially I though we make it a compiler error to do destructure the same property from both runes: let { a: foo } = $props();
let { a: bar } = $props.bindable(); // error, cannot destructure in both Turns out, we need to allow it because it would mean preventing people from doing something like this, which might be crucial to not introduce unwanted behavior: let { value: _, ...rest } = $props(); // rest should not contain value
let { value } = $props.bindable(); More generally, it's a gotcha one might need to watch out for, that using a The other thing that doesn't feel quite right - to me at least - is that from a types perspective, it's best to declare the props type once and then share it: type Props = { foo: string; bar: number }
let { foo }: Props = $props();
let { bar }: Props = $props.bindable(); I can't really articulate why, but this looks like everything is bindable, because the type before Make it part of $props()I'm therefore contemplating whether or not an alternative design is better: Keep the definition of what is bindable part of
|
My objections to On an aesthetic level, using strings like It also feels less structured. Finally, it feels less consistent with the way we do things elsewhere:
The technical objections relate to this question:
Either answer is deeply weird:
Either way — syntactically restricted or not — we face the possibility of future extensions to |
😄 |
Maybe type Props = { foo: string; bar: string; }
let { foo } = $props.bindable();
let { ...props } = $props(); // { bar: string } |
How about having That would make the examples of @dummdidumm work. Here it's possible to raise an error: let { a: foo } = $props();
let { a: bar } = $props.bindable(); // error, cannot destructure in both But with let { a: foo } = $props.all();
let { a: bar } = $props.bindable(); //no-error
// foo is read-only |
Hmm... What is exactly wrong with? let { a, b = $state(), c = $state(42) } = $props(); where
@Rich-Harris, you have mentioned that a similar approach have been considered and had downsides. Could you elaborate what are those? |
Svelte's whole thing is syntax sugar and using the compiler to help you, why not have a special label for bindable props? Alternatively, using colon to indicate it's bound. let { unbound, :bound } = $props()
let { unbound: foo, :bound: bar } = $props() |
@wbhob, the syntax must be a valid JS/TS, so it's no-go. Talking about let { bindable }: { bindable: boolean } = $props(); I prefer the single-rune solution, though I agree with Harris' objections. I wonder what are the cases with the dynamic list of bindable props. |
One thing that might be worth noting is that, while Svelte components aren't web components, web components have a special
I don't think any of these ideas are ideal, but maybe they'll help figure out a better idea. I'll also just add that I think having an object passed to runes for configuration would be bad - unless the other runes were changed to not be "properties" of runes and use objects themselves. But I don't like that option either, really. EDIT: fixed the repl link - not sure what happened. |
1 suffers the same problems as $ props () .bindable (...). Plus, it's a weird way to configure behaviour for people not familiar with custom elements, and I'm sure more people are familiar with another framework than with custom elements. What about let {
foo,
bar,
bindable: {
value,
baz,
...bindableRest,
},
} = $props(); Syntactically restricted, no configs, no renaming problem. |
While my solution may not be ideal, I think labelling the prop in some way in its primary declaration will be more effective than redeclaring it or declaring it separately |
Lets look at how the typing complexity is, by removing all letters
Becomes
A crazy total of 23 special characters! And a total of 9 unique characters ({}:=$();.). Wonder how the vibes on that typing is! Typing that would be like doing a dance on the keyboard, but I mean, dancing is a good vibe.
becomes
A total of 6 special characters! 3 unique character (:=;) |
Except you have to repeat |
Hard to find the best workaround when you're facing the language limitations. There is clearly a need for decorators here (in a destructured assignment): let {
cantBind = "",
@bindable canBind,
}: {
cantBind: string,
canBind: string
} = $props() That's what decorators are for: labelling properties. Also, if Typescript would allow type definitions right in the object, that would help a lot of frontend frameworks to keep dry:
Which is the theoretical best syntax for declaring props. Interestingly, Flow recently implemented the second syntax. Good job guys finding your way out of this maze. The language designs are not helping you. |
Uh oh!
There was an error while loading. Please reload this page.
Describe the problem
Today, every component prop is bindable. That means that if a component does
let { foo } = $props()
, a consumer can dobind:foo
and observe changes tofoo
inside the component.That's fine — bindings are a useful feature, and no-one has really complained about this — but it's not ideal:
This issue is more salient in Svelte 5 than previously: because reactivity is based on signals rather than static analysis, mutating an object can have distant effects. We mitigate this by warning the developer (in dev mode) against mutating state owned by another component, but the way to prevent that warning is to pass props around with
bind:
. Unless we add a modicum of friction, the temptation will be to use bindings willy-nilly, which is the way of spaghetti.Describe the proposed solution
We plan to introduce a new rune,
$props.bindable()
. Usage is identical to$props()
, but all the identifiers declared within are considered bindable:A user of this component could do
value={value}
orbind:value={value}
, butbind:type={type}
would cause an error (both a type error and a runtime error).Bindings would continue to exist in
...rest
properties......but would be readonly, as they are today.
(We considered a variety of alternative designs — annotating prop interfaces with a
Bindable
type, adding an option to the$props(...)
call, using defaults (let { value = $bind() } = ...
) and so on, but they all had major downsides.)Importance
nice to have
The text was updated successfully, but these errors were encountered: