5.5 KiB
title |
---|
Stores |
[!NOTE] Prior to the introduction of runes, stores were the primary state management mechanism for anything that couldn't be expressed as component state or props. With runes — which can be used in
.svelte.js/ts
files as well as in components — stores are rarely necessary, though you will still sometimes encounter them when using Svelte. See When to use stores below.
A store is an object that allows reactive access to a value via a simple store contract. The svelte/store
module contains minimal store implementations that fulfil this contract.
Inside a component, you can reference the store's value by prefixing it with the $
character. (You cannot use this prefix to declare or import local variables, as it is reserved for store values.)
The component will subscribe to the store when it mounts, and unsubscribe when it unmounts. The store must be declared (or imported) at the top level of the component — not inside an if
block or a function, for example.
If the store is writable, assigning to (or mutating) the store value will result in a call to the store's set
method.
<script>
import { writable } from 'svelte/store';
const count = writable(0);
console.log($count); // logs 0
count.set(1);
console.log($count); // logs 1
$count++;
console.log($count); // logs 2
</script>
Store contract
To be considered a store, an object must implement the Readable
interface. It can additionally implement the Writable
interface. Beyond that, a store can include whatever methods and properties it needs.
Readable stores
A readable store is an object with a subscribe(fn)
method, where fn
is a subscriber function. This subscriber function must be immediately and synchronously called with the store's current value upon calling subscribe
, and the function must be added to the store's list of subscribers. All of a store's subscribers must later be synchronously called whenever the store's value changes.
The subscribe
method must return a function which, when called, removes the subscriber.
// @filename: index.ts
import type { Readable } from 'svelte/store';
const readable = {} as Readable<any>;
// ---cut---
const unsubscribe = readable.subscribe((value) => {
console.log(value);
});
// later
unsubscribe();
[!NOTE] Advanced: if a second argument is provided to
subscribe
, it must be called before the subscriber itself is called. This provides a mechanism for glitch-free updates.
Writable stores
A writable store is a readable store with set
and update
methods.
The set
method takes a new value...
// @filename: index.ts
import type { Writable } from 'svelte/store';
const writable = {} as Writable<string>;
// ---cut---
writable.set('new value');
...while the update
method transforms the existing value:
// @filename: index.ts
import type { Writable } from 'svelte/store';
const writable = {} as Writable<string>;
const transform = (value: string) => value;
// ---cut---
writable.update((value) => transform(value));
Generally, these methods will have a synchronous effect but this is not required by the contract — for example in the case of spring
and tweened
it will set a target value, and subscribers will be notified in requestAnimationFrame
callbacks.
RxJS interoperability
For interoperability with RxJS Observables, the subscribe
method is also allowed to return an { unsubscribe }
object rather than the function itself.
Note that unless observable.subscribe(fn)
synchronously calls fn
(which is not required by the Observable spec), Svelte will see the value of $observable
as undefined
until it does.
Creating custom stores
In general, you won't be implementing the subscription logic yourself. Instead, you will use the reference implementations in svelte/store
even if you expose a custom interface (demo):
<!--- file: App.svelte --->
<script>
import { writable } from 'svelte/store';
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update((n) => n + 1),
decrement: () => update((n) => n - 1),
reset: () => set(0)
};
}
const counter = createCounter();
</script>
<h1>count: {$counter}</h1>
<button onclick={counter.increment}>increment</button>
<button onclick={counter.decrement}>decrement</button>
<button onclick={counter.reset}>reset</button>
When to use stores
Stores are still a good solution when you have complex asynchronous data streams or it's important to have more manual control over updating values or listening to changes. If you're familiar with RxJs and want to reuse that knowledge, the $
also comes in handy for you.