CORRECT006 · 孤立した $effect
重大度: critical · カテゴリ: correctness
チェック内容
Section titled “チェック内容”コンポーネント初期化の外で実行されることが確定している $effect / $effect.pre 呼び出しを検出します — これらはランタイムで Svelte の effect_orphan エラーを投げます:
.svelte.ts/.svelte.jsの runes モジュール、または.svelteの<script module>ブロックのトップレベルの effect — モジュールの import 時に実行され、どのコンポーネントの初期化コンテキストにも属しません。- constructor で裸の
$effect($effect.rootで包まれていないもの)を作るクラス(同一ファイル内で宣言)のモジュールスコープでのnew— 共有状態マネージャのパターンです。検出位置はnewの行になります。
検出対象外: 関数内の effect(ファクトリ関数・IIFE を含む)、$effect.root(...) コールバック内の effect、コンポーネント内でのみインスタンス化されるクラス、他ファイルから import されたクラス、クラスフィールド初期化子や static ブロック内の effect、名前のないクラス式(const Store = class { … })内の effect。検出は関数境界を決して越えないため、構造上誤検出はありません — その代わりクロスファイルやファクトリ経由のケースは検出できません。
トップレベルの if や、コンストラクタ引数によるチェック(constructor(persist) { if (persist) $effect(...) })で条件付きにガードされた effect は、そのガードが実行時に決して真にならない場合でも検出されます — ガードは静的に評価できないためです。ガードが意図的なものであれば、インラインの抑制コメント(svelte-vitals-disable-next-line CORRECT006)を使ってください。
Svelte コンパイラはこれらのパターンをすべて警告なしでコンパイルします — 失敗はランタイムでのみ起こります。開発中は気づかないことがあり(そのモジュールが特定のルートでしか import されない場合など)、本番ではクラッシュとして顕在化します — 典型的にはそのモジュールを import するすべてのページで 500 エラーになります。リアクティブな effect はコンポーネントの初期化中、または明示的な $effect.root スコープ内でしか作れません。
class QuizStateManager { bookmarks = $state<string[]>([]); constructor() { // ❌ ランタイムで effect_orphan — モジュールスコープにコンポーネントコンテキストはない $effect(() => { saveToStorage(this.bookmarks); }); }}export const quizState = new QuizStateManager();$effect.root でスタンドアロンのリアクティブスコープを作るか(アプリ全体と同じ寿命ならそのままで可、そうでなければ返り値のクリーンアップ関数を確実に呼ぶこと):
constructor() { $effect.root(() => { $effect(() => { saveToStorage(this.bookmarks); }); });}または effect のセットアップをコンポーネント初期化時に行うよう構造を変えます:
class QuizStateManager { bookmarks = $state<string[]>([]); startPersisting() { $effect(() => { saveToStorage(this.bookmarks); }); }}export const quizState = new QuizStateManager();<script> import { quizState } from '$lib/store.svelte.js'; quizState.startPersisting();</script>