仮想DOMに隠されたコスト
この10年間、仮想DOM(VDOM)はパフォーマンスの黄金律(ゴールドスタンダード)だと言われてきました。Reactは、DOMを直接操作するよりもメモリ上のツリーで変更を計算する方が高速であるという考えを広めました。それはしばらくの間、優れた抽象化として機能していました。
しかし、モダンなWebアプリが複雑になるにつれ、その抽象化がボトルネックになってきました。特に状態(state)が変わるたびに、フレームワークはコンポーネントツリー全体を「比較(diff)」することを強いられます。速度を維持するために、エンジニアはエンジンが不要な処理をしないよう、useMemoやuseCallbackの調整に追われることになります。
私のチームは最近、リアルタイム分析ダッシュボードを構築している際にこの限界に直面しました。徹底的な最適化を行っても、50列のデータテーブルは60fpsを維持するのに苦労し、更新が激しい時には15fpsまで落ち込むこともありました。その不満から私たちはSolidJSに辿り着きました。そして、問題は私たちのコードではなく、VDOMベースのフレームワークの根本的なアーキテクチャにあることにすぐに気づきました。SolidJSはVDOMを完全に無視し、コードを直接的かつ精密なDOM更新へとコンパイルする手法を選択しています。
SolidJSの哲学:一度だけ実行し、永遠に更新する
ReactからSolidJSに乗り換えて最初に気づくのは、コンポーネントの振る舞いの違いです。Reactでは、コンポーネントは繰り返し実行される関数です。対照的に、SolidJSのコンポーネントは、一度だけ実行されるセットアップスクリプトです。リアクティブなグラフを初期化した後は、実質的に消滅します。状態が変化したとき、その状態に紐付いた特定の式だけが再評価されます。
このアプローチは単なるギミックではありません。本番環境で大きな成果をもたらします。ブラウザに重い差分検知エンジンを送る必要がなくなったため、メインのバンドルサイズは約30%削減されました。処理すべきVDOMが存在しないため、モバイルデバイスでのTime-to-Interactive(TTI)指標は400ms以上改善しました。SolidJSはJSXを効率的なDOM命令にコンパイルし、モダンなフレームワークの構文でありながらバニラJavaScriptのような速度を提供します。
Signals APIを理解する
SignalsはSolidJSの核となる仕組みです。createSignalはReactのuseStateに似て見えますが、その下のロジックは異なります。Signalは自身のサブスクライバー(購読者)を追跡する「監視可能な値」です。単なる値を得るのではなく、ゲッターとセッターを取得します。
import { createSignal } from "solid-js";
const [count, setCount] = createSignal(0);
// 関数として呼び出すことで値にアクセスします
console.log(count());
count()を呼び出すことで、Solidはその値がUIのどこに存在するかを正確に把握します。setCountを呼び出しても、フレームワークはコンポーネント全体を再実行しません。その Signal にリンクされた HTML 内の特定のテキストノードのみを更新します。この細粒度のリアクティビティ(fine-grained reactivity)こそが、SolidJSが速度とメモリ効率のベンチマークで常にトップを走り続ける理由です。
派生ステートとエフェクト
Solidは、重い計算用にcreateMemoを、副作用用にcreateEffectを提供しています。Reactで見られるような「依存配列」の悪夢を忘れることができます。SolidJSは実行中にどのSignalが使用されたかを自動的に追跡します。そして、それらの特定のSignalが変更されたときにのみ、エフェクトを再実行します。
import { createSignal, createMemo, createEffect } from "solid-js";
const Counter = () => {
const [count, setCount] = createSignal(1);
// Solidは自動的に count() の依存関係を「検知」します
const doubleCount = createMemo(() => count() * 2);
createEffect(() => {
console.log("現在のカウント:", count());
});
return (
<button onClick={() => setCount(count() + 1)}>
カウント: {count()} | 2倍: {doubleCount()}
</button>
);
};
実践:リアクティブなリストの構築
パフォーマンスの差を実感するには、フレームワークがリストをどのように処理するかを見てください。VDOMフレームワークでは、1,000個のアイテムがあるリストの1つを更新する際、しばしば1,000個すべてのアイテムをチェックする必要があります(これはReact仮想化などの手法が推奨される大きな理由です)。Solidは<For>コンポーネントを使用します。これは、不要な比較を行わずにDOMノードを個別に処理するために専用設計されたものです。
プロジェクトのセットアップ
Viteテンプレートを使えば、数秒でプロジェクトを開始できます。ターミナルを開いて以下を実行してください。
npx degit solidjs/templates/ts my-solid-app
cd my-solid-app
npm install
npm run dev
効率的なデータテーブルの実装
行が毎秒更新されるリアルタイムの価格トラッカーを想像してみてください。行全体をちらつかせることなく、価格のセルだけを更新したいはずです。以下は、私たちの本番環境の実装を簡略化したものです:
import { createSignal, For, onCleanup } from "solid-js";
const PriceTracker = () => {
const [stocks, setStocks] = createSignal([
{ id: 1, name: "AAPL", price: 150 },
{ id: 2, name: "TSLA", price: 700 },
{ id: 3, name: "GOOGL", price: 2800 },
]);
// 1000ミリ秒ごとに価格を更新
const interval = setInterval(() => {
setStocks(prev => prev.map(s => ({
...s,
price: s.price + (Math.random() - 0.5) * 10
})));
}, 1000);
onCleanup(() => clearInterval(interval));
return (
<div>
<h2>ライブ株価</h2>
<ul>
<For each={stocks()}>
{(stock) => (
<li>
{stock.name}: <strong>${stock.price.toFixed(2)}</strong>
</li>
)}
</For>
</ul>
</div>
);
};
export default PriceTracker;
<For>コンポーネントは、リストの順序が変わった場合に、SolidがDOMノードを再作成するのではなく移動させることを保証します。価格だけが変わった場合は、その特定の<strong>タグのみが更新されます。リストの残りの部分は完全に静的なままです。
なぜ次のプロジェクトにSolidJSが最適なのか
6ヶ月間本番運用して得られた最大の収穫は、単なる速度ではなく「思考の明快さ」です。古いクロージャ(stale closures)や複雑なフックのルール、あるいは堅牢なReactロジックを構築する際の苦労を心配する必要はもうありません。D3やLeafletのようなネイティブライブラリを使いたい場合も、フレームワークに邪魔されることなく、本物のDOMノードに直接アクセスできます。
エコシステムも整っています。SolidStartは強力なSSR機能を提供し、Solid Routerはナビゲーションをスムーズに処理します。データ量の多いアプリ、リアルタイムエディタ、複雑なダッシュボードを構築しているなら、仮想DOMは支払う必要のない「パフォーマンスの負債」です。
JSXの構文が馴染み深いため、Reactからの移行は簡単です。しかし、その根底にあるロジックは、ブラウザの実際の仕組みにはるかに近いです。クリーンで宣言的なバニラJavaScriptを書いているような感覚になります。
結論
SolidJSは、より効率的なWebへの転換を象徴しています。重い処理をコンパイル段階に移し、精密な更新のためにSignalsを使用することで、VDOMフレームワークでは到達できないパフォーマンスを提供します。私の経験上、これは単なるわずかな向上ではありません。より機敏なインターフェースと、予測可能な開発体験をもたらします。JavaScript疲れを解消し、ユーザーが受けるべきパフォーマンスを手に入れるためにフレームワークと戦うことに疲れたなら、今こそSolidJSを試すべき時です。

