When using SvelteKit, the store
must always be created in the route page
/layout
, otherwise there will be problems with the status being up-to-date after refreshing.
You can't create a store
in an imported module, because the server-side module only loads once, and this causes errors in the store
.
All this leads to a certain situation with types.
The problem:
I create and pass the store from page.svelte
to Child.svelte
, via setContext
:
<!-- page.svelte -->
<script>
import Child from "./Child.svelte";
import { writable } from 'svelte/store';
import { setContext } from "svelte";
function createCount() {
/** @type {import("svelte/store").Writable<number>} */
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const count = createCount();
setContext("store", count);
</script>
<Child/>
<!-- Child.svelte -->
<script>
import { getContext } from "svelte";
const count = getContext("store");
</script>
<h1>The count is {$count}</h1>
<button on:click={count.increment}> </button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>
In Child.svelte
the type for store
count is any
:
And it should be as in the place of the declaration:
That's what it should be the type of store
, returned from getContext("store"):
const count: {
subscribe: (this: void, run: Subscriber<number>, invalidate?: Invalidator<number> | undefined) => Unsubscriber;
increment: () => void;
decrement: () => void;
reset: () => void;
}
Passing via <Child {count}/>
has the same problem.
QUESTION: How can this be done?
Alternative
It doesn't. You can only create a store
in a module, and import it - then the types are preserved.
But this creates some problems with store
validity, as I wrote about at the beginning.
CodePudding user response:
There are two primary options: Use preprocess-svelte
and write TypeScript or add JSDoc type annotations.
With TS:
<script lang="ts">
import { getContext } from 'svelte';
// Prop
export let count: StoreType;
// Context
const store = getContext<StoreType>('context');
</script>
With JSDoc:
<script>
import { getContext } from 'svelte';
// Prop
/** @type {StoreType} */
export let count;
// Context
/** @type {StoreType} */
const store = getContext('context');
</script>
You can declare types in separate .d.ts
files or use @typedef
.
The createCount
function can be extracted to a separate file without issue. That way its return type can also be used when typing props/contexts. E.g.
// create-count.js
import { writable } from 'svelte/store';
export function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n 1),
decrement: () => update(n => n - 1),
reset: () => set(0),
};
}
<script>
import { getContext } from 'svelte';
/** @type {ReturnType<import('./create-count').createCount>} */
export let count;
/** @type {ReturnType<import('./create-count').createCount>} */
const store = getContext('context');
</script>
In TS this is cleaner, since you can just import the type as part of regular code and use type aliases.
<script lang="ts">
import type { createCount } from './create-count';
import { getContext } from 'svelte';
export let count: Count;
const store = getContext<Count>('context');
type Count = ReturnType<typeof createCount>;
</script>