Published 05-21-2023
Summary
For context, if you’re new to svelte, a very basic example of reactive statements
let a = 1
let b = 1
$: c = a + b; // 2
a = a + 1
would reactively update c
to 3
And without reactivity, c
would still be equal to 2.
Any top-level statement can be made reactive by prefixing it with the
$:
JS label syntax. Reactive statements run after other script code and before the component markup is rendered, whenever the values that they depend on have changed. Source
“Layering” reactive statements can change when subsequent reactive statements execute. (layering is not a technical or official term)
For example, if I want to fetch some data only when my notebook_id
changes, I can layer my reactive statements like this.
export let data;
$: ({notebook_id} = data)
$: { loadData(notebook_id) } // Fires when *id* changes
This code will only execute when the notebook_id
value changes. In other words, unrelated changes to data
, won’t cause loadData
to fire
Context: A
+page.svelte
file can have a sibling+page.ts
that exports a load function, the return value of which is available to the page via thedata
prop Source
For contrast, the code below, would execute loadData
every time data
changes, even if notebook_id
has the same value
export let data;
$: { loadData(data.notebook_id) } // Fires when *data* changes
Here is a similar example using stores and reactive statements
A store is simply an object with a subscribe method that allows interested parties to be notified whenever the store value changes Source
In this example, we’re calling refreshEditor
whenever the value
prop on notebook_store
changes.
import { notebook_store } from 'notebook'
export let data;
$: ({notebook_id} = data)
$: active_notebook = $notebook_store[notebook_id];
$: current_value = active_notebook?.value
$: if (current_value) {
// Only executes when current_value changes
refreshEditor(current_value);
}
For contrast, the following statement would execute when any property on notebook_store
updates.
$: if ($notebook_store.current_value) {
refreshEditor($notebook_store.current_value);
}
If refreshEditor
is expensive (in my real-world use-case it’s updating a CodeMirror instance) we only want to run it when we need to.
Not realizing this was the case when I first started using Svelte-Kit, I had littered my app with statements like this. (don’t do this)
let last_notebook_id = ''
$: {
if (data.notebook_id && last_notebook_id != data.notebook_id) {
loadData(data.notebook_id)
last_notebook_id = data.notebook_id
}
}
Aside from being much more verbose, I think this code is more error prone
When you navigate around your application, Svelte-Kit reuses existing layout and page components. Source
i.e. Svelte-Kit will re-use the page instead of destroying and recreating it. While the example above is probably okay, I often worry I’ll introduce some hard-to-find bug when I write code like this in my pages.
+page.ts
load
functions work in a similar way on navigation.
Svelte-Kit tracks the dependencies of each load function to avoid re-running it unnecessarily during navigation. Source
The code below will re-execute during navigation if params.notebook_id
has changed.
So for example, if we navigated from notebook/123
to notebook/456
the load function would execute. Because notebook_id
has changed.
But going from notebook/456
to notebook/456/child-route
would not re-execute.
export const load = ({ params }) => {
return {
notebook_id: params.notebook_id,
streamed: {
notes: fetchNotes(params.notebook_id)
}
};
};
This works without any sort of “layering”. In other words, this executes on navigation when notebook_id
changes. Not when any params
property changes
WITNESS MY BEAUTIFUL REACTIVITY PASSED DOWN THE ARMSTRONG FAMILY LINE FOR GENERATIONS! — Major Alex Louis Armstrong