Reputation: 4156
go to REPL here: https://svelte.dev/repl/27a2cff81ebb4000970eee32747cc6c2?version=3.20.1
open the console
uncomment line 27 ($: canSubscribe && $store
)
Expected:
I thought that the $store
can subscribe (with the $:
) only if canSubscribe
is true.
The question is: Why $store
subscribe if canSubscribe
is false?
Am I wrong?
Upvotes: 2
Views: 2586
Reputation: 25041
Am I wrong?
Yes. Don't blame yourself though, your expectation seems only logical to me. But that's not the way it works.
As a general rule, if there is a $
prefixed variable somewhere in your component's code, then it must be a store, and it will be subscribed right away on component creation, and unsubscribed when the component is destroyed.
A minor exception to this rule was introduced quite recently though (with this PR). I'll let you follow the trail down the rabbit hole if you want to know the whole discussion. The crux of it is that, now, a store subscription must be a store or nullish (that is, null
or undefined
-- see this comment).
This means it is now possible to hack your way into the behavior you expected, if needed. We're coming back to this.
Why $store subscribe if canSubscribe is false?
Because stores are subscribed right away. My understanding, from the discussion in the issues linked above, is that it is for performance (byte size) and sanity (fail fast and visibly if someone tries to subscribe to something that is not a store). Makes sense to me.
Now, back to the question you didn't ask: how to subscribe only when/if needed? Put the store in the auto subscribed variable only when needed, and keep it nullish otherwise.
Don't do:
$: started && $store
Do this instead:
$: proxyStore = started ? store : null
$: console.log($proxyStore)
Full example (REPL):
<script>
import { writable } from 'svelte/store'
const state1 = { subscribed: 0, unsubscribed: 0 }
const store1 = writable(42, () => {
state1.subscribed++
return () => {
state1.unsubscribed++
}
})
const state2 = { subscribed: 0, unsubscribed: 0 }
const store2 = writable(43, () => {
state2.subscribed++
return () => {
state2.unsubscribed++
}
})
let started = false
$: started && $store1
$: targetStore = started ? store2 : null
$: $targetStore
</script>
<pre>
started = {started}
store1 = {$store1} {JSON.stringify(state1)}
store2 = {$targetStore} {JSON.stringify(state2)}
</pre>
<button on:click={() => {started = !started}}>
{started ? 'Start' : 'Stop'}
</button>
Upvotes: 3
Reputation: 2047
Svelte walks the AST at compile time to determine auto-subscriptions.
It sets up a subscription even when the code accessing the store is unreachable.
For example:
import {foo} from './stores'
let condition = false
if (condition) { $foo }
Even though $foo
is technically unreachable, that won't be known until runtime.
Alternative: You can always use a manual subscription to work around this. Example:
import {onDestroy} from 'svelte'
import {myStore} from './stores.js'
// subscribe when component is created
const unsubscribe = myStore.subscribe(value => {
// this is called anytime the value of myStore changes
})
// make sure to unsubscribe when component is unmounted
onDestroy(unsubscribe)
Upvotes: 4
Reputation: 16451
In the reactive statement $: canSubscribe && $store
you have an expression to be evaluated. Since it is reactive, Svelte has to determine when to re-evaluate this expression and this will happen on two occassions: when canSubscribe
changes, or when $store
changes. So it will have to make a subscribtion to both of those values, hence you see that you immediately get a subscriber in your code.
Note that we often do canDoSomething && canDoSomething()
in JavaScript but that is not 100% the same as doing if (canDoSomething) { canDoSomething()}
it just has, in most cases, the same effect.
Upvotes: 3