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-global と
correctness/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'
}
};