Closed18

solidjsを試してみる

nazo6nazo6

仮想DOMはパフォーマンスが悪いみたいな記事を見てへーと思って非仮想DOMのなにかを試してみようと思った
この分野だと間違いなくSvelteが一番ポピュラーで人気上昇中だがどうしてもあのテンプレート構文的なのが好きになれない・・・
というわけで見つけたのが
https://github.com/solidjs/solid

Reactと同じようにjsxが使えてそれでいて仮想DOMを使わないライブラリとのこと。もうすぐ1.0リリースらしい。もうrcに到達してる

nazo6nazo6

公式サイトにももちろん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はビルドが爆速で素晴らしい

nazo6nazo6

トップページを見るといろいろよさげな事が書いてある。
リアクティブプログラミングや単方向データバインディングなどのReactの哲学に従いつつ仮想DOMではない方法でレンダリングをしているとのこと。
また、コンポーネントは1度しか実行されず、hooksとバインディング(effectとかのこと?)が再実行されるらしい。Reactはコンポーネントが再実行されるんだったような気がするからそこが違いなんだろうか

Resoucesのページにはsolidjsの作者の記事がいろいろ載っている。
https://dev.to/this-is-learning/components-are-pure-overhead-hpm

nazo6nazo6

まだ開発中のようだが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が返ってくる。

nazo6nazo6

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と同じような感じかな?

nazo6nazo6

制御フロー

ここで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コンポーネントを使わなくてもいいということだろうか?

nazo6nazo6

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を使うとよいとのこと。

nazo6nazo6

次に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の型が正常に判定されないためかなりビミョーだと思った。
https://github.com/solidjs/solid/issues/199
一応ワークアラウンドはあるようだがいちいちこれを書くのかという感じでうーん

nazo6nazo6

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には関数コンポーネントしかないはず

nazo6nazo6

ライフサイクル

solidのライフサイクルメソッドにはonMountonCleanupがある。
特筆すべきことはなさそう?

nazo6nazo6

バインディング

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 }} />
nazo6nazo6

class

classNameではなく普通にclassが使える、またclassListというものもある。

<button
	class={current() === 'foo' ? 'selected' : ''}
	onClick={() => setCurrent('foo')}
>foo</button>

<button
	classList={{selected: current() === 'foo'}}
	onClick={() => setCurrent('foo')}
>foo</button>
nazo6nazo6

ref/forwardRef

特に難しいことをしなくても使える。useRefみたいなのはいらない。

let myDiv;

<div ref={myDiv}>My Element</div>
nazo6nazo6

カスタムディレクティブ

(element, valueAccesor)を受け取る関数をインポートして接頭辞としてuse:を関数名の前につけたディレクティブを設定することでカスタムディレクティブを使える。ReactにはないがVueとかにはあるようだ。
便利そうではあるが標準から外れているのでサンプルではdead codeとして削除されないようにしてたりするなどちょっと面倒臭いことがありそう。

このスクラップは2021/06/05にクローズされました