-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
feat: add $state.opaque
rune
#14639
base: main
Are you sure you want to change the base?
feat: add $state.opaque
rune
#14639
Conversation
🦋 Changeset detectedLatest commit: 21b0865 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
preview: https://svelte-dev-git-preview-svelte-14639-svelte.vercel.app/ this is an automated message |
|
TODO: when we land |
Given the syntactical requirements, I assume there's no way to make this work with classes? |
Nope. I think that’s fine though |
Couldn't something like this be done? (apologies for the terrible spacing) class Thing{
constructor() {
([this.stuff, this.invalidate] = $state.opaque(stuff));
}
} Couldn't the private property for the |
I don't think we should optimise for the usage of |
What if $state.opaque is allowed to work with both a destructured assignment and also with a direct assignment, e.g. // direct assignment that compiles into an object usage:
let count = $state.opaque(0);
// changing state would be:
// the prop name could be named differently
// but that's what's being used in Svelte in other places, e.g. `value`
count.current++;
// update,
// the default name could by the variable name with pre-pended `update`, e.g. `updateCount`
count.update();
// or object destructure with optional renaming, this will generate the same code as it does now in this pr
let { current: count, update: updateCount } = $state.opaque(0);
count++;
updateCount(); class for the non-destructured declaration would be: (otherwise, people can use a constructor with destructured) // from:
class Thing {
count = $state.opaque(0); // if count is declared as private `#count`, nothing is generated as per usual.
}
// to:
class Thing {
#_count = $state.opaque(0);
get count() {
return this.#_count.current;
}
set count(v) {
this.#_count.current = v;
}
// if `update` already exists could name it `updateCount`
// a bit more complicated but `$state.opaque()` can take a second parameter `options`
// e.g. $state.opaque(myState, {updateName: 'updateCount', propName: 'current'})
update() {
this.#_count.update();
}
} Typescript will show the prop names and it would be documented. What are the benefits vs the current approach?When using a non-destructured variant, it will allow people to keep the update method together with the state. As a side-effect, if kept non-destructured, this would keep the reactivity intact for crossing function boundaries. See It's possible to generate a class definition. What are the cons vs the current array destructure approachIf destructuring and renaming variables, it's a bit more verbose.Later, maybe also introduce a rune // somewhere
let count = $state.opaque(0);
// somewhere else
let { current: count, update: updateCount } = $state.unwrap(count);
// but in reality the generated code would still be referring to and updating `count.current`
// so only one state exist and there is no de-synchronization. other states: // somewhere
let something = $state({ count: 0 })
// somewhere else
let { count } = $state.unwrap(something);
// the generated code would still be referring to and updating `something.count` |
That's far too many new APIs for something we don't want to promote as a core pattern. |
It's great, even if I still think a simple |
It seems a little odd to me to provide this as a rune when there's a similar pattern that doesn't seem all that difficult to implement manually at the moment. I may be missing something, but what's the benefit of using The real kicker, to me, would be an implementation of |
I think you might be missing the core issue here. This approach would
intertwine your own data handling intricately with Svelte, similar to how
you'd have to encapsulate everything in Ember Data. This could become
problematic for users who manage their data separately from Svelte. The
beauty of pre-version 5 Svelte was its neutrality—it didn't impose any data
management patterns on you. I'm increasingly leaning towards not upgrading,
as I see no tangible benefits in doing so. It feels like Svelte is drifting
towards other frameworks, prioritizing ideology over practicality which might be
very good for other people, but very bad for somes.
…On Thu, Dec 19, 2024, 08:54 Pascal Schuster ***@***.***> wrote:
It seems a little odd to me to provide this as a rune when there's a
similar pattern that doesn't seem all that difficult to implement
manually at the moment
<#10560 (comment)>.
I may be missing something, but what's the benefit of using $state.opaque
over this sort of helper class?
The real kicker, to me, would be an implementation of $state.from that
has been proposed (#12956
<#12956>). It seems to me like a
much more flexible way to provide an escape hatch for interfacing with
external libraries. In conjunction with the above code for the helper class
(or the opaque rune if it's a better solution), by providing a setter
that calls the invalidate function, you could *almost* treat external
state like native state, which would be vastly superior DX to having to
manually call invalidateFoo every single time you update foo.
—
Reply to this email directly, view it on GitHub
<#14639 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAJVOG6FDSFXPIVIYKDYKRD2GJ3RRAVCNFSM6AAAAABTJ3QHQ2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDKNJTGAYDCMJXGA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Leaving aside that that doesn't really have anything to do with this PR, I fail to understand how this sort of approach would link your data handling inextricably with Svelte? The entire point of the Yes, it was much simpler in Svelte 4, and yes, I agree something should be done to allow for at least the emulation of said simplicity. But that's what this PR is ultimately about, is it not? |
It kind of does, I will let you read the task this PR closes : #14520 |
I have found a minor issue with this: With regular |
That’s because re-assignment isn’t a way to update state with thus rune. The only way is using the invalidate function. |
This PR adds the
$state.opaque
rune. This is a special kind of rune designed to solve problems with handling and managing state from external sources/libraries.Specifically, for cases where Svelte is not involved in understanding that of the reactivity of the thing you're passing in – thus being opaque to Svelte. In order to let Svelte 5 know that something has changed, an invalidate function is provided so you can manually control letting Svelte know it should invalidate any reactive dependencies on this piece of state:
This means that reassignments and mutations to opaque state will not trigger reactive updates. You always need to invoke the invalidate function. Furthermore, this means that you will likely need to adopt the recent function bindings feature if you intend to use opaque state with
bind:something
.Closes #14520.