typescript - Hur sätter jag typen för en anpassad store när jag skickar store via setContext eller komponentprop?


När du använder SvelteKit måste store alltid skapas i routen page/layout, annars uppstår problem med att statusen är uppdaterad efter uppdatering av sidan.
Du kan inte skapa ett store i en importerad modul, eftersom server-sidans modul bara laddas en gång, och detta orsakar fel i store.
Allt detta leder till en viss situation med typer.

Problemet:

Jag skapar och skickar butiken från +page.svelte till 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>

I Child.svelte är typen för store count any:

Och det ska vara som på platsen för deklarationen:

Det är vad det borde vara typen av store, som returneras från getContext("store"):

const count: {
    subscribe: (this: void, run: Subscriber<number>, invalidate?: Invalidator<number> | undefined) => Unsubscriber;
    increment: () => void;
    decrement: () => void;
    reset: () => void;
}

Att skicka via <Child {count}/> har samma problem.

FRÅGA: Hur kan detta göras?

Alternativ

Det gör det inte. Du kan bara skapa en store i en modul och importera den - då bevaras typerna.
Men detta skapar vissa problem med store-giltighet, som jag skrev om i början.

Svar #1

Det finns två huvudsakliga alternativ: Använd preprocess-svelte och skriv TypeScript eller lägg till JSDoc-typannoteringar.

Med TS:

<script lang="ts">
    import { getContext } from 'svelte';

    // Prop
    export let count: StoreType;

    // Context
    const store = getContext<StoreType>('context');
</script>

Med JSDoc:

<script>
    import { getContext } from 'svelte';

    // Prop
    /** @type {StoreType} */
    export let count;

    // Context
    /** @type {StoreType} */
    const store = getContext('context');
</script>

Du kan deklarera typer i separata .d.ts-filer eller använda @typedef.


Funktionen createCount kan extraheras till en separat fil utan problem. På så sätt kan dess returtyp också användas vid typning av props/context. T.ex.

// 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>

I TS är detta renare, eftersom du bara kan importera typen som en del av vanlig kod och använda typalias.

<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>

Relaterade artiklar