-
-
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
feat: make deriveds writable #15570
feat: make deriveds writable #15570
Conversation
🦋 Changeset detectedLatest commit: b5fed42 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 |
|
The issue is closed, but I want to add a note for future readers. I'm new to Svelte 5, but I think making derived writable violates user's expectations and will prevent future optimizations (both compile time and runtime). This comes from viewing derived as internal-nodes in a dataflow network. |
You can always define the variable as let count = $state(0);
const double = $derived({ v: count*2 });
double.v = 42; which, mentally, can not so much differ from writable derived. |
This solution has more issues and does not fully solve the mentioned issues, especially #14536 in combination with (sveltejs/kit#12568). Since server data is not fully reactive, this solution will not update the state of a nested property with in a data item fetched from the server, where as the ugly workaround let { data } = $props();
let myData = $derived(data.myData);
$effect(() => {
myData = data.myData;
}); (or similar proposed workarounds) will update also the nested properties of |
I've not tested with SvelteKit and how it works with the Here is the updated REPL with commented out solution using @paoloricciuti workaround from before. https://svelte.dev/playground/10615e463dfd4620bb97c11ae9d7394e?version=5.25.1 |
Could the PR description be updated? I think the docs example is much more intuitive to explain why this is useful. https://svelte.dev/docs/svelte/$derived#Overriding-derived-values <script>
let { post, like } = $props();
let likes = $derived(post.likes);
async function onclick() {
// increment the `likes` count immediately...
likes += 1;
// and tell the server, which will eventually update `post`
try {
await like();
} catch {
// failed! roll back the change
likes -= 1;
}
}
</script>
<button {onclick}>🧡 {likes}</button> |
I've linked to the docs |
Just curious, why not something like |
Why add another concept when you can just use const and make js do it's work? |
Here's an alternative solution. One way to view Svelte 5 is that it is building a dataflow network, which is a directed acyclic graph ("DAG"). The $states are input nodes to the graph and $derived are internal nodes. If $derived can be written, that turns them into input nodes like $state. That would get rid of nice properties, like being written in only one location in the code, and prevent compiler optimizations in the future. I think a better approach would be to declare some props as input nodes. That is, fully proxied values like $state(). There is already a way to declare props as "$bindable". I suggest adding a new syntax to declare them "$state". Then the example of @hyunbinseo becomes:
This code is clear. It doesn't need a separate |
And it would be specific to props, which we're trying to avoid — it's better to have a primitive that can be used in different contexts (e.g. deeply nested properties of props rather than just the top-level props themselves) than to overload a concept like props. Under the hood your
That's just not true! The compiler can very easily see which deriveds are written to and which aren't. DAGs look good on a whiteboard. But in day-to-day programming it's often very useful to have an ephemeral local copy of something. Svelte has always prioritised Getting Shit Done over any kind of ideological purity, and this is a reflection of that. |
I agree, there may be performance considerations if we just apply it to just top-level props.
That is one possible implementation of it. I don't think it is the only one.
Okay, true. But now we have 3 types of runes: state, derived, and writeable derived. When the compiler considers to apply an optimization, it needs to consider the relationships of all of those. So, the compiler got a whole lot more complicated, for a case rare enough that you didn't foresee it when you made the move to Signals months ago. Writeable derived is not equivalent to any combination of state and derived, because the timing of writes matters. I think that is a very important consideration to think on. Writeable derived must be implemented by run-time code and cannot have compile-time implementations. And that applies to anything downstream of them, like effects.
Okay. I'm an engineer at heart, but I like when math can bring clarity to a problem. And, in my experience, DAGs seem the right math here. They have a long history in compilers and in dataflow programming. And I don't think using a DAG model restrict what programmers can do. I think it would help programmers find clarity by identifying inputs (states) and keeping them separate from compute values (derived). I understand writeable drived are handy. And I'm new here. But they seem a big change. Calculating which effects to execute must be done at runtime, because the timing of assignments now matters. Debugging will not depend on the current state of everything, but the order of assignments. If the compiler wants to exploit parallelism, it needs to have timestamps for assignments to make sure that it gets the right value out. We've been considering the derived state that is initialized by a prop at creation and then every other write is done by 1 statement. But that's a special case for writeable derived. If a writeable derived is written in multiple places in the code, are there race conditions? Could the execution order of effects matter? As I said, writable derived seems like a big change. If one of Svelte's advantages is a compiler, this feature makes that compiler less applicable and more complicated. I would expect that to be a priority too. |
Meantime, the PR diff: |
See sveltejs/cli#487 (comment) for an example of why this is useful.
Docs: https://svelte.dev/docs/svelte/$derived#Overriding-derived-values
Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.packages/svelte/src
, add a changeset (npx changeset
).Tests and linting
pnpm test
and lint the project withpnpm lint