Svelte Module Context

tags: Svelte
category: Front-End
description: Svelte Module Context
created_at: 2023/04/19 01:00:00

cover image


回到 手把手開始寫 Svelte


前言

Module Context,雖然有 Context 字樣,不過使用方式差滿多,雖然也是有點共享(Context)的味道。

只是前面所提到的 Context 是上下的傳遞,而這邊的 Module Context 比較像是平行的傳遞,又或者是把東西 export 出去給其他想用的地方使用。


基本使用

假設我有多個 Counter,然後希望他們共用同一個狀態,可以這樣做:

Counter.svelte

<script lang="ts" context="module">
    import { writable } from 'svelte/store';

    let sharedValue = writable(0);
</script>

<div>
    <button on:click={() => $sharedValue++}>
        Clicked {$sharedValue}.
    </button>
</div>

然後像這樣使用他:

<script lang="ts">
    import Counter from '../components/Counter.svelte';
</script>

<Counter />
<Counter />
<Counter />

這時你任意去戳,他們三個 Counter 的值都會是同步的。

有一種情境可能是有多個組件,你希望點任一個(active),然後其他變成(inactive)

例如我有多張圖片(div代替),點擊任一個後要放大,其他變回原本大小:

<script lang="ts" context="module">
    let activeImage: HTMLElement | null = null;
</script>

<script lang="ts">
    export let color = '#39f';

    let image: HTMLElement | null = null;
</script>

<div
    style="background: {color}; width: 100px; height: 100px; border: 1px solid #333; transition: .5s"
    bind:this={image}
    on:mousedown={() => {
        activeImage?.classList.remove('active');
        activeImage = image;
        activeImage?.classList.add('active');
    }}
/>

<style>
    :global(.active) {
        width: 150px !important;
        height: 150px !important;
    }
</style>

這邊使用了 !important,只是為了 Demo,一般情況請不要使用這個。除非你很清楚你在幹嘛

然後在 style 的部分使用到一個前面沒提過的東西(:global()),這個是因為 Svelte 預設情況下 style 都是以組件為單位的,而且他經由 compiler 先去做處理,因為上面我增減 class 是透過原生 javascript 去處理的,所以 Svelte 並不知道我有用到他(active)。

或是也可以把上面的範例改一下,改為用 Svelte 的方式去做:

<script lang="ts" context="module">
    import { writable } from 'svelte/store';

    let activeImage = writable<HTMLElement | null>(null);
</script>

<script lang="ts">
    export let color = '#39f';

    let image: HTMLElement | null = null;
</script>

<div
    style="background: {color}; width: 100px; height: 100px; border: 1px solid #333; transition: .5s"
    bind:this={image}
    on:mousedown={() => ($activeImage = image)}
    class:active={$activeImage && $activeImage === image}
/>

<style>
    .active {
        width: 150px !important;
        height: 150px !important;
    }
</style>

上面這樣改乾淨許多,而且 Svelte 也能輕易知道我有使用到 .active 這個類別,就不用加上 global 了。

不過需要注意的是,activeImage 必須是 reactive 的,因為這樣其他組件才知道他修改了。


Export 出去給其他地方用

有些時候我可能有組件裡面的東西想要 export 出去給其他地方用,例如前幾篇提到的 Context,如果我就不想要抽離一個單獨檔案去做,想寫在組件裡面,又想用 Symbol 當作 key 的話,就可以這麼做。

這時 +page.svelte 非常簡單:

<script lang="ts">
    import Outer from '../components/Outer.svelte';
</script>

<Outer />

Outer.svelte

<script context="module">
    export const key = Symbol();
</script>

<script>
    import { setContext } from 'svelte';
    import Inner from './Inner.svelte';

    setContext(key, 'Outer');
</script>

<div>Outer</div>
<Inner />

Inner.svelte

<script lang="ts">
    import { getContext } from 'svelte';
    import { key } from './Outer.svelte';

    const value = getContext(key);
</script>

<div>Inner value: {value}</div>

這樣就可以做更多活用了。


總結

到這邊單純的 Svelte 大概就結束了,完結灑花

Q: 但不是還有 Router 沒寫嗎?

A: 那個會在之後 SvelteKit 去寫,雖然其實這個系列一開始就是建立 SvelteKit 專案,只是只用 Svelte 的功能

不過下一篇其實就會是 Router 了,為了做最後的總練習(要跟 Vue 3 對應的話)。




最後更新時間: 2023年04月19日.