コンテンツにスキップ
svelte-vitals
日本語
Esc
移動開く⌘Jプレビュー
このページの内容

correctness/effect-as-derived · 状態の導出に effect を使用

状態を代入するだけの $effect は $derived に置き換えましょう。

重大度: warning · カテゴリ: correctness

チェック内容

コンポーネントのインスタンススクリプトで、本体が $state への代入のみの $effect を検出します。

なぜ重要か

$effect で状態を同期する書き方(React の useEffect の習慣の持ち込み)は、レンダリング後に実行されるため、余計なレンダリングパスやループを招きます。$derived は同じ依存関係を宣言的に表します。個別の effect 実行をスケジュールするのではなく、次に読み取られたタイミングで遅延評価されます。

修正方法

<script>
  let count = $state(0);
  // $effect(() => { double = count * 2; }); の代わりに
  let double = $derived(count * 2);
</script>

既知の制限: mount フラグ / hydration ガードとしての effect

このチェックは「effect の本体が $state への代入だけか」という構造的な判定であり、意味的な判定ではありません。そのため、本来の「導出すべき値を effect に書いてしまう」アンチパターンと、SSR/プリレンダー時と hydration 後の不一致を防ぐための「mount signal」イディオムを区別できません。

<script>
  let mounted = $state(false);
  $effect(() => {
    mounted = true;
  });
  // SSR/プリレンダー時とクライアントの最初のレンダリングでは false のままである必要があります。
  // そうしないと hydration 不一致が起きます。$derived(canVibrate()) は hydration 中に
  // 即座に評価されてしまい、この $effect が防ごうとしているまさにそのちらつきを再発させます。
  const showVibrationToggle = $derived(mounted && canVibrate());
</script>

$derived は hydration 中も含めて次に読まれたタイミングで再計算されますが、$effect はコンポーネントが DOM にマウントされた後にのみ実行されます。それがこのパターンの狙いです。この形を $derived に置き換えるのは $effect が防いでいたバグの再発なので、「修正」せず svelte-vitals-disable-next-line コメントで抑制してください。

既知の制限: browser global の捕捉

同じ構造的な死角は、correctness/server-browser-globalcorrectness/instance-browser-global が示している修正例 、つまり browser 専用の global を読んで $state に代入する $effect も検出してしまいます。

<script>
  let stored = $state(null);
  $effect(() => {
    stored = localStorage.getItem('filters'); // ここで検出されるが「修正」してはいけない
  });
</script>

$derived は、その derived な値が何かに読まれた時点で初めて式を評価します。ただ、この値は元々読まれる ため(典型的にはテンプレートから)に存在し、テンプレートからの読み取りは SSR 中にも起こります。これを $derived(localStorage.getItem('filters')) に置き換えると、サーバーサイドレンダリング中に誰かがそれを 読んだ瞬間、この2つのルールが防ごうとしている ReferenceError: localStorage is not defined を再発させます。 localStorage/window などは、まさに $derived が安全に読めない値です。クライアント限定だと保証できる 読み取りが存在しないからです。onMount、あるいは window のプロパティなら svelte/reactivity/window を使ってください (修正方法はこの2つのルールのドキュメントを参照)。$derived に切り替えるのではなく、この指摘は抑制してください。

モードによる違い

ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。

無効化

個別に抑制するには、対象行の直前に <!-- svelte-vitals-disable-next-line correctness/effect-as-derived --> を置きます。ルールごと無効化するには、次のように設定します。

export default {
  rules: {
    'correctness/effect-as-derived': 'off'
  }
};