👁️
SolidJSにおけるcreateEffectとbatch
はじめに
SolidJS では、createSignal
で定義したシグナル(状態)が変化すると、自動的に依存している createEffect
が再実行されます。本記事では、複数のシグナルを同時に更新したい場合に、意図せずcreateEffect
が途中段階で発火してしまう現象と、その解決策として提供されているbatch
関数の使い方を解説します。
背景
以下のようなシンプルなカウンター&ログ出力コンポーネントを例にします。
import { createSignal, createEffect, For } from "solid-js";
export default function Demo() {
const [countA, setCountA] = createSignal(0);
const [countB, setCountB] = createSignal(0);
const [logs, setLogs] = createSignal<string[]>([]);
createEffect(() => {
setLogs(prev => [
...prev,
`Effect fired: A=${countA()} B=${countB()}`,
]);
});
return (
<div>
<button onClick={() => {
setCountA(a => a + 1);
setCountB(b => b + 1);
}}>
Increment A and B
</button>
<ul>
<For each={logs()}>
{log => <li>{log}</li>}
</For>
</ul>
</div>
);
}
- 初期マウント時に
createEffect
が走り、A=0 B=0
のログが追加される。 - ボタンを押すと
setCountA
→setCountB
の順で更新が走る。
問題の再現
ボタンを1回クリックすると、以下のように意図しない中間状態がログに残ります。
- A=0 B=0 (マウント時)
- A=1 B=0 (countA 更新後)
- A=1 B=1 (countB 更新後)
今回はログに積むだけなので良いですが、createEffect
の内容次第では致命的なバグになりかねません。
なぜ起こるのか
SolidJS のリアクティブシステムは、シグナルが更新されると即座にその依存エフェクトを再実行します:
-
setCountA
が呼ばれる -
countA
に依存しているcreateEffect
が再実行 → この時点のcountB()
はまだ古い値 - 続けて
setCountB
が呼ばれる - さらに
createEffect
が再実行 → 両方の値が新しくなった状態
これにより、2回のエフェクト発火が起き、中間状態がログに残ってしまうわけです。
解決策:batch を使う
SolidJS が提供するbatch関数で、複数のシグナル更新をひとまとめにできます。内部では更新の発火を保留し、まとめて反映させた後に一度だけエフェクトを走らせます。
import { createSignal, createEffect, For, batch } from "solid-js";
export default function Demo() {
const [countA, setCountA] = createSignal(0);
const [countB, setCountB] = createSignal(0);
const [logs, setLogs] = createSignal<string[]>([]);
createEffect(() => {
setLogs(prev => [
...prev,
`Effect fired: A=${countA()} B=${countB()}`,
]);
});
return (
<div>
<button onClick={() => {
batch(() => {
setCountA(a => a + 1);
setCountB(b => b + 1);
});
}}>
Increment A and B
</button>
<ul>
<For each={logs()}>
{log => <li>{log}</li>}
</For>
</ul>
</div>
);
}
このようにすると、ボタン1回のクリックでA=1 B=1 だけがログに追加され、中間状態は発生しません。
on
ユーティリティ
おまけ 特定のシグナルだけをトリガーにしたい場合は、createEffect(on(...))
を使って依存関係を明示的に定義できます。
import { createSignal, createEffect, on } from "solid-js";
createEffect(on(
[countA, countB],
([a, b]) => {
setLogs(prev => [
...prev,
`Effect fired: A=${a} B=${b}`,
]);
}
));
-
on
の第1引数に依存シグナルの配列 - 第2引数でまとめて取得した値を扱う
これならbatch
を使わずとも、複数シグナルの同時反映後に一度だけエフェクトが走ります。
まとめ
- SolidJS の
createEffect
はシグナル更新ごとに即座に走る - 複数更新を同時にまとめたいときは
batch
を使うと一度だけ発火 - より細かい制御には
on
ユーティリティも活用可能
以上です。リアクティブな更新タイミングを正しく制御することで、思わぬバグやパフォーマンス劣化を防げます。それでは快適なSolidJSライフを!ここまでお読みいただきありがとうございます。
Discussion