Reputation: 41
I am writing a Sveltekit app with Svelte 5 after taking a course from Niklas Fischer. The pattern is as follows: Mostly all state is contained in a global State class. In routes/+svelte.layout.ts
the State class is instantiated and then put in a context with setContext
. In various +page.svelte files the State is conveniently obtained with state = getContext(key)
. Quite often the State is destructured, e.g. let { statevar1, statevar2 } = $derived(state)
. It must be $derived
to work, just destructuring the state does not work, nor destructuring $state(state)
. I can understand what
let doubled = $derived(count*2)
means, but here we are not changing the state variables, and worse, we may not change them later in the code, as you may not assign to derived values.
I wrote a small test to find out what works and what not. In test.svelte.ts
class Counter {
cnt = $state(1);
constructor(start: number) {
this.cnt = start;
}
inc() {
console.log('inc before', this.cnt);
this.cnt += 1;
console.log('inc after', this.cnt);
}
init() {
console.log('init before', this.cnt);
this.cnt = 100;
console.log('init after', this.cnt);
}
}
export const counter1 = new Counter(10);
export const counter2 = $state({
cnt: 50
});
In +page.svelte
<script lang="ts">
import { counter1, counter2 } from './test.svelte';
let { cnt: cnt1a } = counter1;
let { cnt: cnt1b } = $state(counter1);
let { cnt: cnt1c } = $derived(counter1);
let { cnt: cnt2a } = counter2;
let { cnt: cnt2b } = $state(counter2);
let { cnt: cnt2c } = $derived(counter2);
</script>
<h1>Counter1</h1>
<h1>{counter1.cnt} works</h1>
<h1>{cnt1a} fails</h1>
<h1>{cnt1b} fails</h1>
<h1>{cnt1c} works</h1>
<button onclick={() => counter1.init()}>init1 works</button>
<button onclick={counter1.init}>init2 fails</button>
<button onclick={() => counter1.inc()}>inc1 works</button>
<button onclick={counter1.inc}>inc2 fails</button>
<h1>Counter2</h1>
<h1>{counter2.cnt} works</h1>
<h1>{cnt2a} fails</h1>
<h1>{cnt2b} fails</h1>
<h1>{cnt2c} works</h1>
<button onclick={() => counter2.cnt++}>inc2</button>
When this page runs, the buttons init2
and inc2
do not work, because class methods can not be passed as function arguments (what a pity, I assume this is due to JS, not Svelte). With init1
and inc1
only the counter counter1.cnt
is reactive, and the derived counter cnt1c
. But cnt2b
is not, as I had assumed. And cnt1a
is also not reactive, which indicates that destructuring strips off the reactivity. Why is all that so?
In additon: In the class I must initialize with some value, which is immediately overwritten by the constructor. And I must initialize with $state()
, but I must not use $state in the constructor, i.e. I may not write this.cnt=$state(start)
. I have not even tried to call new Counter()
with a $stated variable...
Upvotes: 0
Views: 75
Reputation: 185280
Destructuring copies primitive values. If you don't use $derived
, this only happens once so the variable will never update.
This is analogous to regular assignments; this will also never update:
let cnt1a = counter1.cnt;
A destructuring with $derived
can also be seen as multiple separate assignments using $derived
(which is also what happens internally).
This:
let x = $state({ a: 0, b: 0 });
let { a, b } = $derived(x);
Becomes:
let x = $.proxy({ a: 0, b: 0 });
let a = $.derived(() => x.a),
b = $.derived(() => x.b);
Upvotes: 1