Svelte 5のコンテキストAPI(setContext/getContext)を整理した話

Svelte 5のコンテキストAPI(setContext / getContext)を整理します。propsとstoreに続く3つ目の状態共有の方法で、コンポーネントツリー内だけで状態を共有したいときに使います。


3つの状態共有方法の整理

props   → 親から子へ直接渡す(一方向)
store   → どこからでもimportして使う(グローバル)
context → コンポーネントツリー内だけで共有する(スコープ付き)

contextはstoreとpropsの中間的な存在です。


基本的な使い方

<!-- 親コンポーネント -->
<script lang="ts">
  import { setContext } from 'svelte';

  setContext('theme', 'dark');
</script>
<!-- 子・孫コンポーネント(何階層でも) -->
<script lang="ts">
  import { getContext } from 'svelte';

  const theme = getContext<string>('theme');
</script>

<p>テーマ: {theme}</p>

setContext で登録した値は、そのコンポーネントの子孫からのみ参照できます。親や兄弟コンポーネントからは参照できません。


storeとの違い

store   → アプリ全体で1つの値を共有する
context → コンポーネントツリーごとに独立した値を持てる

リスト内のカードコンポーネントで、カードごとに異なる設定を子孫に渡したいときはcontextが向いています。

<CardA>  ← setContext('color', 'blue')
  <CardHeader />   ← getContext で 'blue' を取得
  <CardBody />     ← getContext で 'blue' を取得

<CardB>  ← setContext('color', 'red')
  <CardHeader />   ← getContext で 'red' を取得
  <CardBody />     ← getContext で 'red' を取得

storeだと全カードが同じ値を参照してしまいますが、contextなら各カードが独立した値を持てます。


リアクティブなcontextの作り方

❌ 直接渡すと初期値のコピーになる

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

  let { color }: Props = $props();

  // ❌ color の初期値がコピーされるだけ
  // color が後から変わっても context は更新されない
  setContext('cardColor', color);
</script>

このように書くと Svelte から以下のエラーが出ます。

This reference only captures the initial value of `color`.
Did you mean to reference it inside a closure instead?
https://svelte.dev/e/state_referenced_locally

✅ getterで包むと常に最新の値を参照できる

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

  let { color }: Props = $props();

  // ✅ getter にすると常に最新の color を参照する
  setContext('cardColor', {
    get color() { return color; }
  });
</script>

これはstoreの get count() { return count; } と全く同じパターンです。Svelte 5ではリアクティブな値を外部に渡すときは常にgetterで包むというルールが一貫して適用されます。


実装例:カードコンポーネント

<!-- src/lib/components/Card.svelte -->
<script lang="ts">
  import { setContext } from 'svelte';
  import type { Snippet } from 'svelte';

  type Props = {
    color: string;
    children: Snippet;
  };

  let { color, children }: Props = $props();

  setContext('cardColor', {
    get color() { return color; }
  });
</script>

<div style="border: 2px solid {color}; padding: 16px; margin: 8px; border-radius: 8px;">
  {@render children()}
</div>
<!-- src/lib/components/CardHeader.svelte -->
<script lang="ts">
  import { getContext } from 'svelte';

  type Props = { title: string };
  let { title }: Props = $props();

  const card = getContext<{ color: string }>('cardColor');
</script>

<h2 style="color: {card.color}; margin: 0 0 8px 0;">{title}</h2>
<!-- 使う側 -->
<Card color="blue">
  <CardHeader title="ブルーカード" />
  <p>このカードのテーマカラーはblueです</p>
</Card>

<Card color="red">
  <CardHeader title="レッドカード" />
  <p>このカードのテーマカラーはredです</p>
</Card>

使い分けのまとめ

方法 スコープ 向いているケース
props 親→子(直接) シンプルな親子関係・汎用UIパーツ
store アプリ全体 ログイン情報・カートなどグローバルな状態
context コンポーネントツリー内 複数インスタンスで独立した状態が必要なとき

まとめ

内容
setContext(key, value) 子孫コンポーネントに値を渡す
getContext<型>(key) 親コンポーネントが設定した値を受け取る
getterパターン リアクティブな値を渡すときはgetterで包む
state_referenced_locally 直接渡すと初期値のコピーになるというエラー
← トップページに戻る