🐷
SolidJS触ってみた
対象読者
- Reactから別のフロントエンドフレームワークへの移行を考えている人
- SolidJSがなんとなく気になっている人
はじめに
社内でReactの乗り換え先としてSolidJSが話題になったので、触ってみました。
そこで、Reactと比較しながら、SolidJSのチュートリアルを進めた結果をまとめてみました。
SolidJSとは
Solid is a modern JavaScript framework designed to build responsive and high-performing user interfaces (UI).
SolidJSとは、レスポンシブでパフォーマンスの高いUIを構築するため設計された、JavaScriptのフレームワークです。それもどうやら仮想DOMを使っていないらしいです!
チュートリアル
Docsには日本語版があり、実際に手を動かしながらチュートリアルを進めることができます。
以下では、チュートリアルに紹介されている機能の中で、特によく使いそうなものやReactとの比較ができそうなものを中心にピックアップしていきます。
createSignal
- Reactの
useState
のような役割 -
useState
の戻り値はstate変数とsetter関数の配列であるのに対して、createSignal
の戻り値はgetterとsetterの2つの関数を持つ配列 - Signalという要素をgetterで読み込み、setterで書き込んでいる
// SolidJS
// この例ではtextがgetterでsetTextがsetterになる
const [text, setText] = createSignal<string>("Hello World!");
// textはgetterなので、呼び出す際はtext()とする必要がある
console.log("text: ", text());
return <div>Text: {text()}</div>;
// React
const [text, setText] = useState<string>("Hello World!");
// textは変数なので、そのまま呼び出せる
console.log("text: ", text);
return <div>Text: {text}</div>;
createEffect
- Reactの
useEffect
のような役割 -
useEffect
では第2引数に依存値の配列を指定する必要があるのに対し、createEffect
では依存関係を自動で読み込むので、依存値の配列を指定する必要がない
// SolidJS
createEffect(() => {
console.log("text: ", text());
});
// React
useEffect(() => {
console.log("text: ", text);
}, [text]);
onMount
- Reactの
useEffect
の依存配列を空にした時のような役割 - 最初のレンダリングが全て完了した後、コンポーネントに対して一度だけ実行される
// SolidJS
onMount(() => {
console.log("mounted!");
});
// React
useEffect(() => {
console.log("mounted!");
}, []);
createMemo
- Reactの
useMemo
のような役割 - Effectと同じく、
useMemo
では第2引数に依存値の配列を指定する必要があるのに対し、createMemo
では依存関係を自動で読み込むので、依存値の配列を指定する必要がない
// SolidJS
const doubleCount = createMemo(() => count() * 2);
// React
const doubleCount = useMemo(() => count * 2, [count]);
Show
- Reactの三項演算子でレンダリングするような役割
-
when
に渡された条件がtrueの場合にchildren
が表示され、falseの場合にfallback
が表示される
// SolidJS
<Show when={bool()} fallback={<p>false</p>}>
<p>true</p>
</Show>
// React
{bool ? <p>true</p> : <p>false</p>}
For
- Reactの
.map
でレンダリングするような役割 - indexは定数ではなく、Signalであるため、呼び出す際は
index()
とする
// SolidJS
<For each={todos()}>
{(todo, index) => (
<div>
{index()}: {todo.text}
</div>
)}
</For>
// React
{todos.map((todo, index) => (
<div key={todo.id}>
{index}: {todo.text}
</div>
))}
Index
- 使い方は
For
とほとんど同じ -
Index
ではindexが定数となり、todoがSignalであるため、呼び出す際はtodo()
とする
<Index each={todos}>
{(todo, index) => (
<div>
{index}: {todo().text}
</div>
)}
</Index>
ForとIndexの使い分け
- プリミティブ(文字列や数値など)を扱う時は
Index
を使用することで、再レンダリングの発生を抑制できる
const [todos, setTodos] = createSignal<{ id: number, text: string }[]>([]);
const handleEdit = (id: number, text: string) => {
setTodos(todos().map((todo) => (todo.id === id ? { ...todo, text } : todo)));
};
// For
// textを更新するたびに再レンダリングが発生する
<For each={todos()}>
{(todo) => {
console.log(`For rendered : ${todo.text}`);
return (
<div>
<input
type="text"
value={todo.text}
onInput={(e) => handleEdit(todo.id, e.target.value)}
/>
</div>
);
}}
</For>
// Index
// textを更新しても再レンダリングは発生しない(変更は反映される)
<Index each={todos()}>
{(todo) => {
console.log(`Index rendered: ${todo().text}`);
return (
<div>
<input
type="text"
value={todo().text}
onInput={(e) => handleEdit(todo().id, e.target.value)}
/>
</div>
);
}}
</Index>
Switch
- switch文のように、条件に応じてコンポーネントを表示する
// SolidJS
<Switch fallback={<Default />}>
<Match when={status() === "loading"}>
<Loading />
</Match>
<Match when={status() === "error"}>
<Error />
</Match>
<Match when={status() === "success"}>
<Success />
</Match>
</Switch>
// React
const Status = (status: "loading" | "error" | "success" | "default") => {
switch (status) {
case "loading":
return <Loading />;
case "error":
return <Error />;
case "success":
return <Success />;
default:
return <Default />;
}
};
{Status(status)}
Dynamic
-
Show
やSwitch
コンポーネントを置き換えることで短い記述で完結できる
// Switch
<Switch fallback={<Default />}>
<Match when={status() === "loading"}>
<Loading />
</Match>
<Match when={status() === "error"}>
<Error />
</Match>
<Match when={status() === "success"}>
<Success />
</Match>
</Switch>
// Dynamicで置き換えるとこうなる
const statuses = {
loading: Loading,
error: Error,
success: Success,
default: Default,
};
<Dynamic component={statuses[status()]} />
class
- HTMLと同じclass属性でクラスを指定する
// SolidJS
<p class="active">active</p>
// React
<p className="active">active</p>
classList
- 条件付きでクラスを簡潔に指定できる
// SolidJS
<h1 classList={{ active: count() > 0 }}>Counter: {count()}</h1>
// classで書くとこうなる
<h1 class={count() > 0 ? "active" : ""}>Counter: {count()}</h1>
// React
<h1 className={count > 0 ? "active" : ""}>Counter: {count}</h1>
ref
- Reactの
useRef
のような役割 - 変数を宣言してそのまま使える
// SolidJS
let containerRef;
<div ref={containerRef}>
...
</div>
// コールバック関数の形にもできる
<div ref={(el) => console.log(el)}>
...
</div>
// React
const containerRef = useRef<HTMLDivElement>(null);
<div ref={containerRef}>
...
</div>
createStore
- 使い方は
createSignal
とほとんど同じ -
createStore
で作成したオブジェクトを更新する際にproduce
を使うと、ピンポイントで変更を反映でき、再レンダリングを抑制できる -
ForとIndexの使い分けで紹介したコードを
createStore
とproduce
を使って書き換えてみると、For
を使ってもIndex
を使っても、textを更新した際の再レンダリングが発生しなくなる - 配列やオブジェクトを扱う際は基本的に
createStore
を使うのが良さそう
const [todos, setTodos] = createStore<{ id: number, text: string }[]>([]);
const handleEdit = (id: number, text: string) => {
setTodos(
(todo) => todo.id === id,
produce((todo) => (todo.text = text))
);
};
// For
// textを更新しても再レンダリングは発生しない(変更は反映される)
<For each={todos}>
{(todo) => {
console.log(`For rendered : ${todo.text}`);
return (
<div>
<input
type="text"
value={todo.text}
onInput={(e) => handleEdit(todo.id, e.target.value)}
/>
</div>
);
}}
</For>
// Index
// textを更新しても再レンダリングは発生しない(変更は反映される)
<Index each={todos}>
{(todo) => {
console.log(`Index rendered: ${todo().text}`);
return (
<div>
<input
type="text"
value={todo().text}
onInput={(e) => handleEdit(todo().id, e.target.value)}
/>
</div>
);
}}
</Index>
さいごに
チュートリアルやドキュメントを通して感じたSolidJSの優れた点と課題となる点をReactと比較しながらまとめていきます。
優れた点
-
シンプルに書ける
- Reactの乗り換え先としてはかなり良さそう
-
再レンダリングを抑制・低減するための仕組みがある
- 仮想DOMを使わないことによって実現しているらしい
- こちらに関しては話題としてかなり重くなるため、今回は深掘りしません
-
createStore
+produce
でのオブジェクトや配列の更新が便利そう(シンプルに書けるのも良い)
- 仮想DOMを使わないことによって実現しているらしい
-
高パフォーマンス
- VanillaJSに次ぐ速さ
課題となる点
- コミュニティが小さく、エコシステムや日本語の情報が充実していない
- コミュニティの規模
-
State of JavaScript 2023 によると、SolidJSを「使ったことがある人」は、21,432人中9%で、「使ったことはないが聞いたことがある人」は、67.7%
- Reactを「使ったことがある人」は、21,619人中84.4%で、「使ったことはないが聞いたことがある人」は、15.5%
-
State of JavaScript 2023 によると、SolidJSを「使ったことがある人」は、21,432人中9%で、「使ったことはないが聞いたことがある人」は、67.7%
-
公式が紹介しているエコシステム
- 200件に満たないが、最低限のものは揃っていそう
- 大規模なプロジェクトになると、辛い部分がでてきそう
- 特にcss・UI フレームワークとの統合で問題になりそう
- 日本語の情報
- SolidJSに関するZennの記事数は36件(2024年9月時点)
- Reactは6874件
-
公式ドキュメントは日本語に対応しているのでなんとかなりそう
- ただし、新しいBeta版のドキュメントは現時点では日本語に対応していない
- SolidJSに関するZennの記事数は36件(2024年9月時点)
- コミュニティの規模
Discussion