mcpField
S
svelte-lifecycle

svelte-lifecycle

github.com/full-stack-skills開発4110 回インストール4 週間前に更新
リポジトリ

svelte-lifecycle について

Svelte 5 開発者向けのスキルガイドです。ライフサイクルフック、ストア、Context、テストに加え、SSR の動作とブラウザー互換性も扱います。

主な機能

  1. 01ライフサイクルフックと DOM 更新
  2. 02writable、readable、derived ストア
  3. 03Context と SSR の状態分離
  4. 04Vitest、Storybook、Playwright によるテスト
  5. 05ブラウザー対応表と例外

活用シーン

  1. 01コンポーネントのマウント・破棄時の副作用管理
  2. 02コンポーネント間でのリアクティブ状態共有
  3. 03ユニット・コンポーネント・E2E テストの作成
  4. 04ブラウザーの最低バージョン要件の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/full-stack-skills/svelte-skills --skill svelte-lifecycle

インストール後、Claude Code で/svelte-lifecycleを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
examples/
context-advanced.md
lifecycle-hooks.md
README.md
store-patterns.md
stores-advanced.md
testing-playwright.md
testing-vitest.md
references/
browser-support.md
context-api-reference.md
lifecycle-hooks-reference.md
README.md
stores-api-reference.md
testing-reference.md

詳細

Svelte のライフサイクル、ストア、コンテキスト、テスト(Svelte 5)

このスキルでは、Svelte 5 の「Runes 以外」でもコアのリアクティブアーキテクチャに属するサブシステムを扱います。ライフサイクルフック(onMount / onDestroy / tick)、ストア(writable / readable / derived / カスタム)、Context API(createContext / setContext / getContext)、およびテスト体系(Vitest のユニットテストとコンポーネントテスト、Storybook、Playwright の e2e)です。また、ブラウザーのサポート対象と例外も扱います。

このスキルを使う場面

  • コンポーネントのマウント後に DOM にアクセスする(onMount)、またはコンポーネントの破棄前にクリーンアップする(onDestroy)
  • 状態変更後に DOM の更新を待つ(tick)
  • import モジュールを使わずにコンポーネント間でリアクティブな状態を共有する(ストアまたは Context を使用)
  • コンポーネント間で非同期ストリームを扱う(Readable/derived ストア)
  • prop drilling の代わりに Context を使い、SSR 時にグローバル状態を汚染しないようにする
  • Svelte コンポーネントの Vitest ユニットテスト/コンポーネントテスト、Storybook のストーリー、または Playwright の e2e を作成する
  • ブラウザーの最低バージョンと例外的な機能を確認する

重要:onMount

onMount(fn) は、コンポーネントが DOM にマウントされた直後に実行されます。ブラウザーでのみ実行されます——SSR 時には呼び出されません。

基本的な使い方
<script>
  import { onMount } from 'svelte';

  onMount(() => {
    console.log('mounted');
  });
</script>
クリーンアップ関数を返す
<script>
  import { onMount } from 'svelte';

  onMount(() => {
    const interval = setInterval(() => console.log('beep'), 1000);
    return () => clearInterval(interval);
  });
</script>

重要な制約:onMount がクリーンアップを正しく返すには、同期関数を受け取る必要があります。async () => {} は常に Promise を返すため、アンマウント時にクリーンアップは呼び出されません。

外部モジュールから呼び出す

onMount はコンポーネントのスクリプト最上位に記述する必要はありません。ただし、コンポーネントの初期化時にのみ呼び出す必要があります。同じモジュール内のヘルパー関数から呼び出すこともできます。

重要:onDestroy

onDestroy(fn) は、コンポーネントが破棄される直前に直ちに実行されます。SSR のコンポーネントでも実行されます——4 つのライフサイクルフックのうち、サーバー側で実行される唯一のフックです。

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

  onDestroy(() => {
    console.log('destroyed');
  });
</script>

一般的な用途:setInterval のクリーンアップ、AbortController による fetch のキャンセル、グローバルイベントリスナーの解除。

重要:tick

tick() は Promise を返します。保留中のすべての状態変更が DOM に反映された後に resolve されます。保留中の変更がない場合は、次の microtask で resolve されます。

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

  async function handle() {
    count = count + 1;
    await tick();
    // 此时 DOM 已更新
    element.scrollIntoView();
  }
</script>

一般的な用途:フォーカス、スクロール、DOM の測定、状態変更後にサードパーティの命令型 API を呼び出すこと。

非推奨:beforeUpdate / afterUpdate

Svelte 4 時代の「コンポーネント全体の更新前後」に実行されるフックです。Svelte 5 ではshim により提供されていますが、runes コンポーネントでは使用できません。代わりに以下を使用してください。

Svelte 4Svelte 5
beforeUpdate(() => {})$effect.pre(() => {})
afterUpdate(() => {})$effect(() => {})

Runes 版は、明示的に読み取られた状態が変化した場合にのみ実行されるため、より正確です。たとえば、チャットウィンドウでは messages が変化した場合にのみ最下部までスクロールし、テーマを切り替えてもスクロール位置はリセットされません。

重要:ストア(writable、readable、derived)

ストアとは「ストア契約」を満たすオブジェクトです。subscribe(fn) → unsubscribe を持つ必要があり、書き込み可能なストアには set(value) も必要です。

<script>
  import { writable } from 'svelte/store';

  const count = writable(0);
  $count;        // 自动订阅,读取当前值
  count.set(1);  // 写入
  $count = 2;    // 语法糖:等价于 count.set(2)
</script>
writable
import { writable } from 'svelte/store';

const count = writable(0, () => {
  // 第一个订阅者订阅时调用
  return () => {
    // 最后一个订阅者退订时调用
  };
});

2 番目の引数は start/stop 関数で、外部接続(WebSocket、タイマー)の確立によく使われます。

readable

外部から set できないストアです。初期値と start 関数を指定します。

import { readable } from 'svelte/store';

const time = readable(new Date(), (set) => {
  set(new Date());
  const id = setInterval(() => set(new Date()), 1000);
  return () => clearInterval(id);
});
derived

1 つ以上のストアから派生させます。

import { derived, writable } from 'svelte/store';

const a = writable(1);
const b = writable(2);
const sum = derived([a, b], ([$a, $b]) => $a + $b);

非同期版では (values, set, update) を受け取り、非同期コールバック内で set / update を呼び出せます。クリーンアップ関数を返すこともでき、第 3 引数で初期値を指定できます。

readonly / get
  • readonly(store):読み取り専用のビューとしてラップします(set / update なし)
  • get(store):一度だけ同期的に読み取ります(内部で購読を開始 → 読み取り → 購読解除するため、頻繁に実行される処理では推奨されません)

ストアと $state の使い分け

Svelte 5 では、runes($state、.svelte.js モジュール)を優先して使うことが推奨されています。

場面推奨
再利用可能なロジックの切り出し.svelte.js ファイル + $state
コンポーネント間での状態共有モジュールレベルの $state オブジェクト
複雑な非同期データストリームストア(readable + タイマー/購読)
RxJS との相互運用ストア($ による自動購読)
コンポーネント間での単純なカウンターどちらでも可

簡単なルール:runes で対応できる場合は runes を使い、ストアは「イベントストリーム/監視可能なシーケンス」に使います。

重要:Context API

Context を使うと、prop drilling をせずに親コンポーネントから任意の深さの子孫コンポーネントへ値を渡せます。

createContext(推奨、Svelte 5.40+)
// context.ts
import { createContext } from 'svelte';
interface User { name: string; }
export const [getUser, setUser] = createContext<User>();

<script>
  import { setUser } from './context';
  setUser({ name: 'world' });
</script>

<script>
  import { getUser } from './context';
  const user = getUser();
</script>

<h1>hello {user.name}</h1>
setContext / getContext(代替手段)

<script>
  import { setContext } from 'svelte';
  setContext('my-key', value);
</script>


<script>
  import { getContext } from 'svelte';
  const value = getContext('my-key');
</script>

キーと値には任意の JS 値を指定できます。createContext を使うと、型安全になり、キーも不要になります。

hasContext / getAllContexts

現在のコンポーネントのコンテキスト階層に特定のキーが存在するかどうかを判定します。getAllContexts() は現在のコンテキストをすべて含む Map を返します。ライブラリの内部実装でよく使われます。

state との組み合わせ

$state オブジェクトを context に設定し、再代入しないでください——リアクティブな接続が壊れてしまいます。


<button onclick={() => counter = { count: 0 } }>reset</button>


<button onclick={() => counter.count = 0}>reset</button>

Svelte は警告を出します。

グローバル state の代替(SSR で重要)

モジュールレベルの $state は SSR ではリクエスト間で共有されます(データ漏えいが発生します)。Context はリクエストごとに分離されるため、ユーザー固有のデータを扱う場合は Context を優先してください。

重要:テスト

Vitest のユニットテスト
// counter.svelte.test.js
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';

test('Counter', () => {
  let count = $state(0);
  count = 1;
  flushSync();
  expect(count).toBe(1);
});
コンポーネントテスト

vite.config.js:

import { defineConfig } from 'vitest/config';
export default defineConfig({
  test: { environment: 'jsdom' },
  resolve: process.env.VITEST ? { conditions: ['browser'] } : undefined
});
import { mount, unmount, flushSync } from 'svelte';
import { expect, test } from 'vitest';
import Component from './Component.svelte';

test('Component', () => {
  const c = mount(Component, { target: document.body, props: { n: 0 } });
  expect(document.body.innerHTML).toBe('<button>0</button>');
  document.body.querySelector('button').click();
  flushSync();
  expect(document.body.innerHTML).toBe('<button>1</button>');
  unmount(c);
});

$effect は mount 時に自動実行されません。テストでは flushSync() を使って、同期的に実行されるようにします。

$effect を使ったテスト
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';

test('Effect', () => {
  const cleanup = $effect.root(() => {
    let count = $state(0);
    let log = [];
    $effect(() => log.push(count));
    flushSync();
    expect(log).toEqual([0]);
    count = 1;
    flushSync();
    expect(log).toEqual([0, 1]);
  });
  cleanup();
});
Storybook

npx sv add storybook で設定します。play 関数でユーザー操作をシミュレートし、アサーションを行います。

<Story name="Filled" play={async ({ canvas, userEvent }) => {
  await userEvent.type(canvas.getByTestId('email'), '[email protected]');
  await userEvent.click(canvas.getByRole('button'));
  await expect(canvas.getByText("You're in!")).toBeInTheDocument();
}} />
Playwright の e2e
// tests/home.spec.js
import { expect, test } from '@playwright/test';
test('h1 visible', async ({ page }) => {
  await page.goto('/');
  await expect(page.locator('h1')).toBeVisible();
});

playwright.config.js で webServer を設定し、プレビューサーバーを起動します。

重要:ブラウザーのサポート

基準となる目標は 2020(Baseline 2020) です。

ブラウザー最低バージョン
Chrome/Edge87
Firefox83
Safari14
Opera73
Opera(Android)62
Samsung Internet14.0
Android WebView87
Internet Explorer非対応
例外的な機能(より新しいバージョンが必要)
機能Chrome/EdgeFirefoxSafari
$state.snapshot989415.4
bind:devicePixelContentBoxSize—93非対応
svelte/animate の flip—126—

Quick Fixes

  • onMount 清理不执行 → 函数是 async;改用同步函数并内部 await。
  • store 引用 UI 不更新 → 必须用 $store 前缀读取;不要解构(const { subscribe } = store 不会自动订阅)。
  • derived 不触发 → 回调里未读取任何 store 值;或数组写法 derived([a,b], ([$a,$b]) => ...)。
  • Context 在 SSR 间泄漏 → 模块级 $state 全局共享;改用 setContext。
  • Vitest 中 document is not defined → test.environment: 'jsdom';或在文件顶部加 // @vitest-environment jsdom。
  • 组件 mount 后状态未生效 → 在 mount 后调用 flushSync()。
  • Playwright 找不到元素 → 检查 webServer.command 是否正确启动;提高 timeout。

Gotchas

  • store 必须在组件顶层声明:不能在 if 块或函数内。
  • 不要给非 store 变量加 $ 前缀——会被识别为 store。
  • onMount 是唯一的"挂载后",没有"渲染后";用 tick() 等待 DOM。
  • onMount 在 SSR 不运行——访问 window / document 必须放 onMount 或 onDestroy(onDestroy 在 SSR 运行)。
  • setContext/getContext 必须同步调用,在 setup 阶段(不能在事件处理器内)。
  • Context 值若为原始值(数字/字符串),变更不触发更新——用 $state 对象或函数 getter。
  • $state.snapshot 浏览器要求 98+ / Safari 15.4+——兼容性发布时检查。
  • 绑定 onMount 的 cleanup 顺序:onMount 卸载时先调 cleanup,再调 onDestroy。

FAQ

Q: Svelte 5 还需要 stores 吗? A: 简单状态用 $state + .svelte.js 模块;stores 在复杂异步流、计时器、外部订阅、RxJS 互操作时仍有价值。

Q: onMount vs $effect 怎么选? A: 需要访问 DOM 节点、启动副作用(fetch、定时器、绑定 window 事件)用 onMount;纯响应式派生/同步副作用用 $effect。

Q: 为什么 onMount 的 async 函数清理不执行? A: async () => {} 总是返回 Promise;onMount 收到非函数就跳过清理。改用同步函数包异步逻辑:

onMount(() => {
  let cancelled = false;
  (async () => {
    const data = await fetch(...);
    if (!cancelled) state = data;
  })();
  return () => { cancelled = true; };
});

Q: Context 替代 props 的时机? A: 当一个值要穿透 3+ 层中间组件、或父组件不直接知道子组件(如 {@render children()})时。

Q: Vitest 单元测试和组件测试区别? A: 单元测试纯逻辑(runes 在 .svelte.js 文件中),无 DOM;组件测试用 jsdom 渲染完整 Svelte 组件。

Q: IE 11 还能用 Svelte 5 吗? A: 不支持。最低 Chrome 87 / Firefox 83 / Safari 14。

Q: SSR 项目用 $state.snapshot 安全吗? A: 服务端是 Node,无浏览器版本要求;仅客户端使用时检查例外表。

Q: Playwright 跑测试前要 build 吗? A: playwright.config.js 中配 webServer: { command: 'npm run build && npm run preview', port: 4173 },Playwright 自动起 preview 服务器。

Examples

文件内容
examples/lifecycle-hooks.mdonMount 清理、onDestroy、tick DOM 测量、deprecated 钩子
examples/stores-advanced.mdwritable/readable/derived/自定义 store、异步流
examples/store-patterns.mdstores vs $state、持久化、跨组件模式
examples/context-advanced.mdcreateContext、setContext、与 state 组合、SSR 安全
examples/testing-vitest.md单元/组件测试、$effect.root、flushSync、context wrapper
examples/testing-playwright.mde2e 测试、Playwright config

References

文件内容
references/lifecycle-hooks-reference.mdonMount/onDestroy/tick 完整签名、SSR 注意事项
references/stores-api-reference.md全部 store API、TypeScript 类型、契约
references/context-api-reference.mdcreateContext/setContext/getContext/hasContext、SSR 行为
references/testing-reference.mdVitest/Storybook/Playwright 完整 setup
references/browser-support.md浏览器矩阵、例外特性、polyfill 指南

コメント