🧐

Svelteのcontextはなぜstoreと共に使う必要があるのか

2023/09/10に公開

はじめに

Svelte初学者です。
Svelteでwebアプリを作っている中で、グローバルな状態管理の方法に大きく2つの方法があることを知りました。
それが今回のテーマである以下の2つです。

これらの使い所はどのように分けたら良いのか、なぜ以下の公式ドキュメントのようにcontextとstoreを共に使う必要があるのかを理解し、整理するために記事を書きました。

https://kit.svelte.jp/docs/state-management#using-stores-with-context

前提

"svelte": "^4.0.5",
"@sveltejs/kit": "^1.20.4"

contextとstoreの違いとは?

まず、簡単に違いを整理します。

「リアクティブ」かどうか

  • contextはリアクティブではない。
  • storeはリアクティブである。

アクセス可能な範囲

  • あるコンポーネントで定義されたcontextは、その下の階層にある子コンポーネントのみがアクセス可能。
  • storeはアプリケーションのどこからでもアクセス可能。

実際に動きを見てみる


試しに、数字と、クリックするとその数字に+1するボタンだけのコンポーネントを作ってみます。
数字はグローバルで状態管理された値を利用します。

context

以下はParentコンポーネントで定義したcontextをChildコンポーネントでも呼び出している実装です。
Childコンポーネントでは、Parentコンポーネントで作成したcontextを取得できていることがわかります。
また、buttonクリックでsetContextを利用して値の更新を試みていますが、変更がDOMに反映されることはありません。

src/components/context/parent.svelte
<script lang="ts">
  import { setContext } from 'svelte';
  const count = setContext('sampleCount', 0);
  function updateCount() {
    setContext('sampleCount', count + 1);
  }
</script>

<button on:click={updateCount}>button</button>
<div>
  <p>Parent: {count}</p>
  <slot />
</div>
src/components/context/child.svelte
<script lang="ts">
  import { getContext } from 'svelte';
  const count = getContext<number>('sampleCount');
</script>

<div>
  <p>Child: {count}</p>
</div>
src/routes/context/page.svelte
<script lang="ts">
  import Child from '$lib/components/contextOnly/child.svelte';
  import Parent from '$lib/components/contextOnly/parent.svelte';
</script>

<Parent>
  <Child />
</Parent>

store(ルートコンポーネントはcontextの場合と同様なので省略)

以下は先ほどcontextでやったことと全く同じことをstoreで実現しています。
しかし、contextを利用した時とは異なり、クリックするたびにDOMが更新されます。
これは、繰り返しになりますがsvelteにおいてstoreがリアクティブな値であるためです

src/components/store/store.ts
import { writable } from 'svelte/store';
export const count = writable(0);
src/components/store/parent.svelte
<script lang="ts">
  import { count } from './store';
  function updateCount() {
    count.set(($count += 1));
  }
</script>

<button on:click={updateCount}>button</button>
<div >
  <p>Parent: {$count}</p>
  <slot />
</div>
src/components/store/child.svelte
<script lang="ts">
  import { count } from './store';
</script>

<div>
  <p>Child: {$count}</p>
</div>

contextと共にstoreを使う(ルートコンポーネントはcontextの場合と同様なので省略)

contextと共にstoreを利用した場合も、今回の例だと動きはstoreと同様です。

src/components/contextStore/contextStore.ts
import { writable, type Writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
type Context = Writable<number>;

export function setCount() {
  const count = writable<number>(0);
  setContext('count', count);
}

export function getCount() {
  return getContext<Context>('count');
}
src/components/contextStore/parent.svelte
<script lang="ts">
  import { setCount, getCount } from './contextStore';
  setCount();
  const count = getCount();
  function updateCount() {
    $count += 1;
  }
</script>

<button on:click={updateCount}>button</button>
<p>Parent: {$count}</p>
<slot />
src/components/contextStore/child.svelte
<script lang="ts">
  import { getCount } from '../context';
  const count = getCount();
</script>

<div>
  <p>Child: {$count}</p>
  <slot />
</div>

contextはなぜstoreと共に使う必要があるのか

ある程度予測はつくかと思いますが・・
結論、contextとstoreを共に使うのはstoreのようにリアクティブにかつ、contextのように限定的にある値を参照したいときが良いのだと考えます。
そして、これは多くのケースで当てはまると思います。

また、正直contextは単体で使う場面があまり想像できませんでした。ご存知の方がいればいただけますと幸いです。

今回は以上です。

参考

https://joyofcode.xyz/svelte-context-with-stores
https://kit.svelte.jp/docs/state-management
https://svelte.jp/docs/svelte
https://svelte.jp/docs/svelte-store
https://developers.redhat.com/blog/2017/06/30/5-things-to-know-about-reactive-programming

Discussion