svux
v0.0.2
Published
Make Svelte stores from Svelte components
Downloads
3
Readme
Svux
Write a Svelte store with Svelte syntax.
Status Experiment :warning:
Why
Write complex store logic with concise & powerful Svelte syntax: reactive variables, and reactive expressions!
Let Svelte track dependencies between your variables and their changes automatically, instead of writing explicit & repetitive chains of derived.
Example
Double.store.svelte
<script>
import { put } from 'svux'
// exported props are made writable
export let x = 0
// reactive vars are made read-only
// NOTE this line is optional, shown for illustration
let double
// use Svelte's magic to sync results
$: double = x * 2
// expose results on the component's store
$: $put({ x, double })
</script>
App.svelte
<script context="module">
import { storify } from 'svux'
import Double from './Double.store.svelte'
const double = storify(Double)
</script>
<!-- read/write to props -->
<input type="number" bind:this={$double.x} />
<!-- read from exposed variables -->
<pre>{$double.double}</pre>
API
import {
// --- Consuming ---
// turns a component into a standalone store
storify,
// --- Authoring ---
// exposes some variables on the component's default store
put,
// Lazy / derived stores
// creates a lazy data source with a lifecycle (start / stop
// only when subscribed)
source,
//
derive,
} from 'svux'
Consumer
import { storify } from 'svux'
storify
Turns a Svelte component into a standalone store.
import { storify } from 'svux'
import Foo from './Foo.store.svelte'
export const foo = storify(Foo)
The return of storify
is a writable store.
An instance of the Foo
component will be created when it first becomes active (that is when its subscribers count goes from 0 to 1) to compute and feed values into this store. The component instance will be destroyed when the last subscriber leaves, and a new one will be created if the store becomes active again. The value of the props present (see put
bellow) in the store will be preserved and reinjected in all such component instances, though.
This store contents is controlled with put
inside the component.
Authoring
import { put, source, derive } from 'svux'
put
, source
, and derive
are "contextual helpers", so you need to use store notation to use them:
$put(...)
$source(...)
$derive(...)
put
Exposes values (variables) on the component's default store (i.e. the store that is produced by storify
).
Use $put
in a reactive expression to keep the store's value with the state inside the component:
export let a = 0
export let b = 0
$: sum = a + b
$: $put({ a, b, sum })
Once storified, this can then be consumed via the store:
$sumStore.a = 1
$sumStore.b = 1
console.logo($sumStore.sum)
Props (i.e. export let
variables) are reinjected in the component when the Svux store is written to.
Writing to variables that are not props / exported is ignored (i.e. simple let
variables, including implicit ones created by reactive statements $: foo = ...
).
source / derive
One thing you gain when writing a complex store logic with Svelte syntax is implicit variable dependencies tracking: changes are propagated automatically, with no need to explicitly declare dependencies, like you have to do with derived stores for example.
One thing you lose when doing so is fine grained activation of just the stores that are needed, thanks to the subscribe chain created by derived stores. In effect, all the stores in a Svelte component are always subscribed to for the lifetime of the component (as opposed to, say, when they are actually used / accessed). And a live Svelte component is "in tension" at all time: changes are propagated instantaneously, and their side effects updated immediately.
This is not a big problem if you only export one (the default) store. However this becomes annoying if you want to expose multiple semi-independent (or completely independent) stores from the same component, in particular if the source data is "expensive" (e.g. API call, setInterval...). In this case, you want these individual stores to be subscribed as needed, only when actually subscribed by a consumer.
Since this use case is not supported natively in Svelte components, the source / derive pattern gives you a mean to achieve it.
source
First you declare a source, that is a writable store with a lifecycle (start / stop functions) attached to it.
let resolution = 1000
const initialValue = 0
const elapsed = $source(initialValue)
const incrementTime = () => {
elapsed = elapsed + 1
}
$: elapsed.start = () => {
const interval = setInterval(incrementTime, resolution)
// start can return a corresponding stop function
return () => {
clearInterval(interval)
}
}
NOTE The lifecycle is declared in a reactive expression, so Svelte will track and recreate the start
function automatically for you when its dependencies change.
Here, we're updating the lifecycle when resolution
changes; but we've been careful not to have the source (i.e. $elapsed
) itself appear in the reactive expression, to avoid recreating the lifecycle anytime the value of the source changes!
A source store can be used normally in reactive expressions to produce intermediate results:
$: minutes = (elapsed * resolution) / 60
Results can then be exposed for consumption, as well as the dependency on some source(s), with $derive
.
derive
$derive
produces a readable store that publishes results from inside the component, and that is intended to be exported for external consumption. When this store is subscribed from outside of the component, it will also activate the lifecycle of all the sources it's depending upon.
const elapsed = $source(initialValue)
...
$: seconds = elapsed * resolution
$: minutes = seconds / 60
// declare & export a derived "outlet" store
//
// NOTE repeating the name in the arguments is optional, but it is required
// for SSR support
//
export const time = $derive('time', elapsed)
// sync its value
$: $time = { seconds, minutes }
The depended source stores will be "activated" only when at least one of their derived outlet stores is subscribed.
A source start
function will be called when any of its derived outlets is first first subscribed (that is, when the total number of external subscribers --- from outside of the component -- among all depending outlets goes from 0 to 1).
A source start
function will also be called anytime the start
function itself changes, but ONLY if the source store is currently active.
The stop
function optionally returned by a start
function will be called when a) the total number of external subscribers to all outlets falls back to 0, or b) before running another (new) start
function.