App.svelte:
<script>
import Comp, {getValue} from "./Comp.svelte";
const derivedValue = $derived.by(getValue);
</script>
<Comp />
<p>
<span>App: clicked {getValue()} times</span>
<span style="color: gray">{Math.random()}</span>
<span>(derivedValue={derivedValue}).</span>
</p>
Comp.svelte:
<script module>
let value = $state(0);
export function getValue() {
return value;
}
</script>
<button onclick={() => ++value}>Clicked {value} times</button>
https://svelte.dev/playground/811fad4e3ead40ab9f20bfce06612ced?version=5.28.2
From reading Passing state into functions:
Since the compiler only operates on one file at a time, if another file imports
count
Svelte doesn’t know that it needs to wrap each reference in$.get
and$.set
I extrapolated that the compiler, when looking at App.svelte, wouldn't know that getValue
references Comp
's internal state. And yet we see that:
derivedValue
reacted to the change: was recalculated and its span was rerendered.getValue
was rerendered.p
or another ancestor.Both observations 1 and 2 are surprising. My questions are:
getValue()
's change and when? Where does this fit in regards to the quoted documentation?getValue()
itself is treated as if it was a $derived
. I mean, the function is declared like a normal function, and yet if I declare a normal variable in App.svelte like let value = getValue();
, value
wouldn't be updated, because it's not $derived
. So it seems like functions get special treatment. So should one think of all state-using functions as implicit reactive derivatives? Is there way to opt out of this (not that I need it now, but what if)? Does untrack
apply?The compiler does not need to know anything about getValue
because the reactivity is communicated at runtime. The internals of the function trigger a signal which Svelte then reacts to. This applies to functions and properties of all kinds and it does not matter how deeply nested the state interaction logic is.
The documentation is about exporting primitive state, which is not possible in a reactive way. Hence it needs to be wrapped in a function access, which you did.
Functions do not behave the same way as $derived
. They will be reactive if they internally reference some kind of state, but unlike $derived
, the result is not cached and effects can re-run, even if the result of the function is unchanged. If the computation is not expensive one can just opt for functions if that is more convenient (e.g. on computed object properties).