SolidJS とは?超高速フロントエンドの全貌を 3 分で解説

フロントエンド開発の世界では、React、Vue、Angular といった確立されたフレームワークが長らく主流でした。しかし、ここ数年で新たな選択肢が登場しています。それが SolidJS です。「また新しいフレームワークか...」と思われるかもしれませんが、SolidJS は従来のフレームワークとは根本的に異なるアプローチを採用し、驚異的なパフォーマンスを実現しています。今回は、この SolidJS の全貌を 3 分で理解できるよう、わかりやすく解説していきます。
SolidJS とは何か
フロントエンドフレームワークの新星
SolidJS は、2021 年に Ryan Carniato 氏によって開発されたモダンな JavaScript フレームワークです。React ライクな記法を採用しながらも、Virtual DOM を使用せず、細粒度リアクティビティ(Fine-grained Reactivity)という独自の仕組みを採用しています。
SolidJS の最大の特徴は、その名前の通り「solid(堅実)」なパフォーマンスです。多くのベンチマークテストで、React や Vue を上回る実行速度を記録しており、特にランタイムパフォーマンスでは他の追随を許さない結果を示しています。
typescript// SolidJSの基本的な記法例
import { createSignal } from 'solid-js';
function Counter() {
const [count, setCount] = createSignal(0);
return (
<div>
<p>カウント: {count()}</p>
<button onClick={() => setCount(count() + 1)}>
増加
</button>
</div>
);
}
上記のコードを見ると、React を知っている方なら馴染みやすい記法だとお分かりいただけるでしょう。
他のフレームワークとの違い
SolidJS と他の主要フレームワークとの主な違いを表にまとめました。
# | 項目 | React | Vue 3 | SolidJS |
---|---|---|---|---|
1 | Virtual DOM | 使用 | 使用 | 不使用 |
2 | バンドルサイズ | 42.2KB | 33.7KB | 7.3KB |
3 | 初回レンダリング | 普通 | 速い | 非常に速い |
4 | 更新性能 | 普通 | 速い | 非常に速い |
5 | 学習コスト | 中程度 | 中程度 | 低い |
6 | TypeScript 対応 | 良好 | 良好 | 標準対応 |
この表からも分かるように、SolidJS は特にパフォーマンス面で圧倒的な優位性を持っています。
なぜ SolidJS が「超高速」なのか
リアクティブシステムの革新
SolidJS の高速性の秘密は、その独自のリアクティブシステムにあります。従来の Virtual DOM ベースのフレームワークでは、状態が変更されるたびに仮想的な DOM 構造全体を再計算し、実際の DOM との差分を検出して更新を行います。
一方、SolidJS は細粒度リアクティビティを採用しています。これは、状態の変更が発生した際に、その変更に依存する最小限の部分のみを直接更新する仕組みです。
typescript// SolidJSの細粒度リアクティビティの例
import { createSignal, createMemo } from 'solid-js';
function ReactiveExample() {
const [firstName, setFirstName] = createSignal('田中');
const [lastName, setLastName] = createSignal('太郎');
// firstNameまたはlastNameが変更された時のみ再計算される
const fullName = createMemo(
() => `${firstName()} ${lastName()}`
);
return (
<div>
<p>フルネーム: {fullName()}</p>
<button onClick={() => setFirstName('鈴木')}>
名前を変更
</button>
</div>
);
}
このコードでは、firstName
またはlastName
が変更された時のみfullName
が再計算され、他の部分には一切影響を与えません。
バンドルサイズの最適化
SolidJS のバンドルサイズは、圧縮後わずか 7.3KB です。これは React の約 6 分の 1 という驚異的な軽量さです。
json{
"name": "solid-app-example",
"version": "1.0.0",
"dependencies": {
"solid-js": "^1.8.0"
},
"devDependencies": {
"vite": "^5.0.0",
"vite-plugin-solid": "^2.8.0"
}
}
このように、SolidJS は依存関係も最小限に抑えられており、プロジェクトの軽量化に大きく貢献します。
ランタイムパフォーマンスの秘密
SolidJS がランタイムで高速に動作する理由は、コンパイル時の最適化にあります。SolidJS はビルド時にコードを分析し、リアクティブな依存関係を事前に構築します。
typescript// コンパイル前のSolidJSコード
function TodoItem(props) {
return (
<div class={props.completed ? 'completed' : ''}>
<span>{props.text}</span>
</div>
);
}
// コンパイル後(簡略化)
function TodoItem(props) {
const _el$ = document.createElement('div');
const _el$2 = document.createElement('span');
// リアクティブな更新のみを設定
createEffect(
() =>
(_el$.className = props.completed ? 'completed' : '')
);
createEffect(() => (_el$2.textContent = props.text));
return _el$;
}
このコンパイル時最適化により、ランタイムでのオーバーヘッドが大幅に削減されます。
SolidJS の特徴と利点
シンプルな記法
SolidJS の記法は、React を知っている開発者なら直感的に理解できます。しかし、よりシンプルで直接的な表現が可能です。
typescript// React風の記法だが、より直接的
import { createSignal, For } from 'solid-js';
function TodoList() {
const [todos, setTodos] = createSignal([
{ id: 1, text: 'SolidJSを学ぶ', completed: false },
{ id: 2, text: 'アプリを作る', completed: false },
]);
const [newTodo, setNewTodo] = createSignal('');
const addTodo = () => {
if (newTodo().trim()) {
setTodos([
...todos(),
{
id: Date.now(),
text: newTodo(),
completed: false,
},
]);
setNewTodo('');
}
};
return (
<div class='todo-app'>
<h1>SolidJS Todo App</h1>
<div class='input-group'>
<input
type='text'
value={newTodo()}
onInput={(e) => setNewTodo(e.target.value)}
placeholder='新しいTodoを入力'
/>
<button onClick={addTodo}>追加</button>
</div>
<ul class='todo-list'>
<For each={todos()}>
{(todo) => (
<li class={todo.completed ? 'completed' : ''}>
<span>{todo.text}</span>
<button
onClick={() => {
setTodos(
todos().map((t) =>
t.id === todo.id
? { ...t, completed: !t.completed }
: t
)
);
}}
>
{todo.completed ? '戻す' : '完了'}
</button>
</li>
)}
</For>
</ul>
</div>
);
}
TypeScript 標準対応
SolidJS は TypeScript で開発されており、型安全性が標準で提供されています。追加の設定なしで、優れた型推論と IntelliSense のサポートを受けられます。
typescript// 型安全なSolidJSコンポーネントの例
import { createSignal, Component } from 'solid-js';
// Propsの型定義
interface UserCardProps {
user: {
id: number;
name: string;
email: string;
isActive: boolean;
};
onUserClick: (userId: number) => void;
}
const UserCard: Component<UserCardProps> = (props) => {
const [isHovered, setIsHovered] = createSignal(false);
return (
<div
class={`user-card ${
props.user.isActive ? 'active' : 'inactive'
} ${isHovered() ? 'hovered' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={() => props.onUserClick(props.user.id)}
>
<h3>{props.user.name}</h3>
<p>{props.user.email}</p>
<span class='status'>
{props.user.isActive
? 'アクティブ'
: '非アクティブ'}
</span>
</div>
);
};
export default UserCard;
学習コストの低さ
React や Vue の経験がある開発者なら、SolidJS は比較的短期間で習得できます。基本的な概念は似ているため、既存の知識を活用しながら新しい機能を学べます。
実際に動かしてみよう
環境構築
SolidJS のプロジェクトを開始する最も簡単な方法は、公式のスターターテンプレートを使用することです。
bash# Yarnを使用してSolidJSプロジェクトを作成
yarn create solid
# プロジェクトディレクトリに移動
cd my-solid-app
# 依存関係をインストール
yarn install
# 開発サーバーを起動
yarn dev
この時、もし以下のようなエラーが発生した場合:
bashError: Cannot resolve dependency tree
npm ERR! ERESOLVE unable to resolve dependency tree
これは、Node.js のバージョンが古い可能性があります。Node.js 16 以上を使用してください:
bash# Node.jsバージョンを確認
node --version
# v16.0.0以上であることを確認
Hello World アプリの作成
まず、シンプルな Hello World アプリから始めましょう。
typescript// src/App.tsx
import { createSignal } from 'solid-js';
function App() {
const [name, setName] = createSignal('World');
return (
<div class='app'>
<h1>Hello, {name()}!</h1>
<input
type='text'
value={name()}
onInput={(e) => setName(e.target.value)}
placeholder='名前を入力してください'
/>
<p>
入力された名前: <strong>{name()}</strong>
</p>
</div>
);
}
export default App;
typescript// src/index.tsx
import { render } from 'solid-js/web';
import App from './App';
render(() => <App />, document.getElementById('root')!);
基本的なコンポーネントの書き方
SolidJS では、コンポーネントは関数として定義します。以下は、より実践的なコンポーネントの例です。
typescript// src/components/WeatherWidget.tsx
import {
createSignal,
createResource,
Show,
} from 'solid-js';
// 天気データの型定義
interface WeatherData {
location: string;
temperature: number;
condition: string;
humidity: number;
}
// 天気データを取得する関数(モック)
const fetchWeatherData = async (
city: string
): Promise<WeatherData> => {
// 実際のAPIコールをシミュレート
await new Promise((resolve) => setTimeout(resolve, 1000));
// エラーシミュレーション
if (city === 'error') {
throw new Error('Weather data not found for this city');
}
return {
location: city,
temperature: Math.floor(Math.random() * 35) + 5,
condition: ['晴れ', '曇り', '雨'][
Math.floor(Math.random() * 3)
],
humidity: Math.floor(Math.random() * 50) + 30,
};
};
const WeatherWidget = () => {
const [city, setCity] = createSignal('東京');
// createResourceを使用してデータフェッチング
const [weatherData] = createResource(
city,
fetchWeatherData
);
return (
<div class='weather-widget'>
<h2>天気情報</h2>
<div class='city-input'>
<label>都市名:</label>
<input
type='text'
value={city()}
onInput={(e) => setCity(e.target.value)}
placeholder='都市名を入力'
/>
</div>
<Show
when={!weatherData.loading}
fallback={
<div class='loading'>
天気データを読み込み中...
</div>
}
>
<Show
when={!weatherData.error}
fallback={
<div class='error'>
エラー: {weatherData.error?.message}
<br />
<small>都市名を確認してください</small>
</div>
}
>
<div class='weather-info'>
<h3>{weatherData()?.location}</h3>
<div class='temperature'>
{weatherData()?.temperature}°C
</div>
<div class='condition'>
{weatherData()?.condition}
</div>
<div class='humidity'>
湿度: {weatherData()?.humidity}%
</div>
</div>
</Show>
</Show>
</div>
);
};
export default WeatherWidget;
css/* src/styles/WeatherWidget.css */
.weather-widget {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #f9f9f9;
}
.city-input {
margin-bottom: 20px;
}
.city-input label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.city-input input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.loading {
text-align: center;
color: #666;
font-style: italic;
}
.error {
background-color: #ffebee;
color: #c62828;
padding: 10px;
border-radius: 4px;
border-left: 4px solid #c62828;
}
.weather-info {
text-align: center;
}
.temperature {
font-size: 2.5em;
font-weight: bold;
color: #2196f3;
}
.condition {
font-size: 1.2em;
margin: 10px 0;
color: #666;
}
.humidity {
color: #888;
}
エラーハンドリングの例として、無効な都市名を入力した場合のエラー表示も実装しています。上記のコードで「error」と入力すると、以下のようなエラーメッセージが表示されます:
kotlinError: Weather data not found for this city
このように、SolidJS ではShow
コンポーネントと組み合わせて、条件付きレンダリングを簡潔に記述できます。
まとめ
SolidJS は、フロントエンド開発における新たな選択肢として、非常に魅力的なフレームワークです。
SolidJS の主な利点:
# | 利点 | 詳細 |
---|---|---|
1 | 圧倒的なパフォーマンス | Virtual DOM 不使用による高速レンダリング |
2 | 軽量なバンドルサイズ | 7.3KB という驚異的な軽さ |
3 | 学習コストの低さ | React 経験者なら短期間で習得可能 |
4 | TypeScript 標準対応 | 追加設定不要の型安全性 |
5 | 細粒度リアクティビティ | 必要最小限の更新による効率性 |
特に、パフォーマンスが重要なアプリケーションや、バンドルサイズを極限まで削減したいプロジェクトでは、SolidJS の導入を強く検討する価値があります。
React や Vue の豊富なエコシステムと比較すると、SolidJS はまだ発展途上の部分もありますが、そのコアとなる技術的優位性は非常に印象的です。今後の Web アプリケーション開発において、SolidJS は間違いなく重要な選択肢の一つとなるでしょう。
ぜひ、実際に SolidJS を触ってみて、その高速性と開発体験の良さを体感してみてください。きっと、フロントエンド開発に対する新たな視点を得られるはずです。
関連リンク
- blog
うちのチーム、これやってない?アジャイル開発を腐らせる、ありがちなアンチパターン 10 選と処方箋
- blog
CD パイプラインを構築して、開発チームを「リリース疲れ」から解放しよう
- blog
見積もりが全然当たらないあなたへ。プランニングポーカーで楽しく、納得感のある見積もりをするコツ
- blog
「QA は最後の砦」という幻想を捨てる。開発プロセスに QA を組み込み、手戻りをなくす方法
- blog
ドキュメントは「悪」じゃない。アジャイル開発で「ちょうどいい」ドキュメントを見つけるための思考法
- blog
「アジャイルコーチ」って何する人?チームを最強にする影の立役者の役割と、あなたがコーチになるための道筋
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
- review
人類はなぜ地球を支配できた?『サピエンス全史 上巻』ユヴァル・ノア・ハラリが解き明かす驚愕の真実
- review
え?世界はこんなに良くなってた!『FACTFULNESS』ハンス・ロスリングが暴く 10 の思い込みの正体
- review
瞬時に答えが出る脳に変身!『ゼロ秒思考』赤羽雄二が贈る思考力爆上げトレーニング
- review
関西弁のゾウに人生変えられた!『夢をかなえるゾウ 1』水野敬也が教えてくれた成功の本質
- review
「なぜ私の考えは浅いのか?」の答えがここに『「具体 ⇄ 抽象」トレーニング』細谷功