I generally don't use classes in any TypeScript I write. I believe functions make things simpler, and you no longer get Tree Shaking with methods in a class.
However, using classes with Runes can actually be faster, because they don't have to compile $state
variables with get
and set
or with value
... they just work. This is what Rich Harris recommends in many cases.
Sharable Rune
We need a sharable Rune class, and of course we must use Context.
// rune.svelte.ts
import { getContext, hasContext, setContext } from "svelte";
type RCurrent<TValue> = { current: TValue };
export class Rune<TRune> {
readonly #key: symbol;
constructor(name: string) {
this.#key = Symbol(name);
}
exists(): boolean {
return hasContext(this.#key);
}
get(): RCurrent<TRune> {
return getContext(this.#key);
}
init(value: TRune): RCurrent<TRune> {
const _value = $state({ current: value });
return setContext(this.#key, _value);
}
// NOT NEEDED!
update(getter: () => TRune): void {
const context = this.get();
$effect(() => {
context.current = getter();
});
}
}
And we can export custom runes where we want.
// counter.svelte.ts
import { Rune } from "./rune.svelte";
export const counter = new Rune<number>('counter');
This is how most people share $state
variables anyway, however, this is safe for the server (see my previous posts in this thread). We must name it like any other context.
Initialize
We must initialize our $state
just like anywhere else, only once, in the parent component.
<script lang="ts">
import { counter } from '$lib/counter.svelte';
const count = counter.init(0);
// `count.current` is available here
</script>
Read Anywhere
We can use it safely in a child component and read the current
method.
<script lang="ts">
import { counter } from '$lib/counter.svelte';
const count = counter.get();
</script>
<h1>Hello from Child: {count.current}</h1>
<button type="button" onclick={() => count.current++}>
Increment From Child
</button>
Update Anywhere
You can update the rune using the current
method as normal.
<script lang="ts">
import { counter } from '$lib/counter.svelte';
const count = counter.get();
count.current = 9;
</script>
Derived Update
Sometimes, you may need to update a value based on another reactive value. 99% of use cases, you should just use the .current
value, but here is an example just in case this applies to you.
<script lang="ts">
import { counter } from '$lib/counter.svelte';
let value = $state(8);
counter.update(() => value);
</script>
<h1>Hello from Child2: {value}</h1>
<button type="button" onclick={() => value++}>
Update From Another State
</button>
I hope you find value,
J