solidjsを試してみる
日本語ドキュメントができました
なのでこんなスクラップを見るよりこっちを見ましょう
仮想DOMはパフォーマンスが悪いみたいな記事を見てへーと思って非仮想DOMのなにかを試してみようと思った
この分野だと間違いなくSvelteが一番ポピュラーで人気上昇中だがどうしてもあのテンプレート構文的なのが好きになれない・・・
というわけで見つけたのが
Reactと同じようにjsxが使えてそれでいて仮想DOMを使わないライブラリとのこと。もうすぐ1.0リリースらしい。もうrcに到達してる
情報があまりなかったのでためらっていたが絶賛制作中の公式サイトを見つけた
おそらく1.0に合わせて公開される気がするのでもうすぐなにもしなくても
から見れるようになる気がする
公式サイトにももちろんsolidjsが使われていて、さらにバンドラーにはViteを使っているようだ。Vite用のプラグインもある。(https://github.com/solidjs/vite-plugin-solid)
ローカルで開発中のサイトを起動する
$ git clone https://github.com/solidjs/solid-site
$ git checkout dev
$ pnpm i
$ pnpm dev
https://localhost:3000 にアクセスするといい感じのサイトが立ち上がっている。
やはりViteはビルドが爆速で素晴らしい
トップページを見るといろいろよさげな事が書いてある。
リアクティブプログラミングや単方向データバインディングなどのReactの哲学に従いつつ仮想DOMではない方法でレンダリングをしているとのこと。
また、コンポーネントは1度しか実行されず、hooksとバインディング(effectとかのこと?)が再実行されるらしい。Reactはコンポーネントが再実行されるんだったような気がするからそこが違いなんだろうか
Resoucesのページにはsolidjsの作者の記事がいろいろ載っている。
まだ開発中のようだがTutorialがしっかり整備されていてMonacoエディタで快適にチュートリアルを進めることができる。
基本のカウンターアプリはこんな感じ。(以下のコードは全てチュートリアルからのコピペ)
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
setInterval(() => setCount(count() + 1), 1000);
return <div>Count: {count()}</div>;
}
render(() => <Counter />, document.getElementById('app'));
createSignal
はパッと見hooksだがsolidjsにはhooksのルールがないので自由な場所に書けるらしい。
またuseState
とは違い、値ではなくgetterが返ってくる。
Effectもある
import { render } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';
function Counter() {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("The count is now", count());
});
return <button onClick={() => setCount(count() + 1)}>Click Me</button>;
}
render(() => <Counter />, document.getElementById('app'));
createMemo
でメモ化もできる。useMemo
と同じような感じかな?
制御フロー
ここでShow
コンポーネントという見慣れない物が登場
<Show
when={loggedIn()}
fallback={<button onClick={toggle}>Log in</button>}
>
<button onClick={toggle}>Log out</button>
</Show>
仮想DOMを使わない代償として、jsxでそのままArray.prototype.map
とかやると全てのコンポーネントが再レンダリングされてしまうため、このような専用コンポーネントを用意しているようだ。
ただSolidのコンパイラは三項演算子をサポートしているとも書いてある。Show
コンポーネントを使わなくてもいいということだろうか?
Showコンポーネントは出し分けをするだけだが他にも制御構造用のコンポーネントがいろいろある。
For
<For each={cats()}>{(cat, i) =>
<li>
<a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}>
{i() + 1}: {cat.name}
</a>
</li>
}</For>
Index
<Index each={cats()}>{(cat, i) =>
<li>
<a target="_blank" href={`https://www.youtube.com/watch?v=${cat().id}`}>
{i + 1}: {cat().name}
</a>
</li>
}</Index>
ForとIndexの違いとして各アイテムとindexのプリミティブとgetterという関係が逆になっている。
恐らくFor
では配列の中身に基いて再レンダリングが行われ、Index
ではindexに基いて再レンダリングが行われている(日本語が下手)
なので非プリミティブな配列にはFor
を使い、プリミティブなものにはIndex
を使うとよいとのこと。
次にSwitch
コンポーネント
<Switch fallback={<p>{x()} is between 5 and 10</p>}>
<Match when={x() > 10}>
<p>{x()} is greater than 10</p>
</Match>
<Match when={5 > x()}>
<p>{x()} is less than 5</p>
</Match>
</Switch>
正直いままでのもテンプレート構文味があって若干微妙に感じていたが、これに関してはtypescriptの型が正常に判定されないためかなりビミョーだと思った。
一応ワークアラウンドはあるようだがいちいちこれを書くのかという感じでうーんPortal
コンポーネント
<Portal>
<div class="popup">
<h1>Popup</h1>
<p>Some text you might need for something or other.</p>
</div>
</Portal>
これはReactのポータルと同じような感じかな
ErrorBoundary
コンポーネント
import { render } from "solid-js/web";
import { ErrorBoundary } from "solid-js";
const Broken = (props) => {
throw new Error("Oh No");
return <>Never Getting Here</>
}
function App() {
return (
<>
<div>Before</div>
<ErrorBoundary fallback={err => err}>
<Broken />
</ErrorBoundary>
<div>After</div>
</>
);
}
render(() => <App />, document.getElementById("app"));
ReactのError Boundaryと同じように例外をキャッチできるものだが、Reactのようにクラスコンポーネントでないと使えないみたいな制限はない。そもそもsolidjsには関数コンポーネントしかないはず
ライフサイクル
solidのライフサイクルメソッドにはonMount
とonCleanup
がある。
特筆すべきことはなさそう?
バインディング
style
<div style={{
color: `rgb(${num()}, 180, ${num()})`,
"font-weight": 800,
"font-size": `${num()}px`}}
>
Some Text
</div>
Reactのようにstyleプロパティにオブジェクトを入れてスタイルを適用できるが、Reactと違いcamelCaseではなく、普通のhtmlで指定するのと同じkebab-caseを使う。
これの利点としてcss変数が使える。なるほど。
<div style={{ "--my-custom-color": state.themeColor }} />
class
classNameではなく普通にclassが使える、またclassListというものもある。
<button
class={current() === 'foo' ? 'selected' : ''}
onClick={() => setCurrent('foo')}
>foo</button>
<button
classList={{selected: current() === 'foo'}}
onClick={() => setCurrent('foo')}
>foo</button>
ref/forwardRef
特に難しいことをしなくても使える。useRefみたいなのはいらない。
let myDiv;
<div ref={myDiv}>My Element</div>
カスタムディレクティブ
(element, valueAccesor)
を受け取る関数をインポートして接頭辞としてuse:
を関数名の前につけたディレクティブを設定することでカスタムディレクティブを使える。ReactにはないがVueとかにはあるようだ。
便利そうではあるが標準から外れているのでサンプルではdead codeとして削除されないようにしてたりするなどちょっと面倒臭いことがありそう。
Props
// あとで読む
🎉1.0
v1.0がリリースされた。
作者記事
公式サイトも公開されていた。これで使う人が増えるかも?