49
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactもSvelteも大好きな私が思うSolidJSの良いところ

Posted at

SolidJSとは

https://www.solidjs.com/
仮想DOMを使わない、Reactライクに書ける新しいフロントエンドのフレームワークです。
同じく仮想DOMを使わないフレームワークとしてSvelteがありますが、Svelteと違ってReactと近い書き方で記述できるのが特徴です。
今回はReactもSvelteもがっつり使っている私が思うSolidJSの良いところ・もうちょっと改善してほしいところを挙げていきます。

SolidJSの良いところ

高パフォーマンス

SolidJSの最大のポイントは仮想DOMを使わずに直接DOMを更新することで高パフォーマンスを発揮することです。
Svelteと同様にコンパイル時点で、このステートが更新されたらこのDOMを更新するというのを紐づけることで仮想DOMいらずにReactのようなアプリケーションを作ることが出来ます。
公式にパフォーマンス比較の画像が載っていますが、Vanillaとほぼ変わらないパフォーマンスが出るそうです。(Svelteよりも高速)
image.png

createSignalが便利

SolidJSではステートの扱いがより柔軟になります。
その一例として、createSignalの柔軟さが挙げられます。
このcreateSignalはReactでいうところのuseStateに近いですが、大きな違いとしてコンポーネントの外でも使えるというところがあります。

import { createSignal } from 'solid-js';

const [outerState, setOuterState] = createSignal('');

const Comp = () => {
    const [innerState, setInnerState] = createSignal('');

    return (
        <div>
            {outerState()}, {innerState()}
        </div>
    );
};

こちらの例では、outerStateはコンポーネントの外、innerStateはコンポーネントの中で定義していますがどちらも同じように扱うことができます。
二つの違いはこのCompというコンポーネントが複数回呼ばれた場合に、outerStateの方は一つのステートを複数のコンポーネントが参照する形になりますが、innerStateの場合はそれぞれのコンポーネントに特有のステートが生成されます。
ReactでいうReduxContextのようにグローバルに近いレイヤーでのステート宣言が可能になります。
(SolidJSの場合はこれとは別にStoreContextの機能があり、また別の使い分けをすることが出来ます。)

createEffectも便利

もう一つ便利な点として、ステートが変更された場合にコールバックを実行するcreateEffectという関数が、ReactのuseEffectと違って二つ目の引数(依存関係の配列)を必要としません。
こちらも柔軟に設定が可能で、例えばbというステートの変更は検知しなくていいが処理の中では使いたいという場合にはuntrackという関数を使うことで可能になります。
(Svelteではこの辺が非常にめんどくさいので、SolidJSはよく考えられて作られていることが伺えます。)

const [a, setA] = createSignal('');
const [b, setB] = createSignal('');

createEffect(() => {
    console.log(a());
});

createEffect(() => {
    const v = a();
    untrack(() => console.log(v, b()));
});

カスタムディレクディブ

Vue/Svelteにあるようなdirective機能もSolidJSでは使えます。
以下の例ではよく実装するようなデータバインディングをしてくれるmodelというdirectiveを作成しています。

const [name, setName] = createSignal('');

function model(el: HTMLInputElement, value: () => Signal<string>) {
    const [field, setField] = value();
    createRenderEffect(() => (el.value = field()));
    el.addEventListener('input', (e) => setField(e.target.value));
}

<input type="text" use:model={[name, setName]} />;

JSXの利便性

SvelteやVueと違い独自の拡張子/フォーマットで書くのではなくJSXでコンポーネントを書けるも便利な点です。
1ファイル1コンポーネントの制約がないので、1ファイルでいくつもコンポーネントを書けます。
またReactで便利だったHOC(高階コンポーネント)のようなことも実装することができます。

const C: Component<{
    testSignal: Signal<string>;
}> = ({}) => {
    return <></>;
};

const wrap = (
    C: Component<{
        testSignal: Signal<string>;
    }>
) => {
    const WrappedComponent: Component<{}> = () => {
        const testSignal = createSignal('');

        return <C testSignal={testSignal} />;
    };

    return WrappedComponent;
};

export const Wrapped = wrap(C);

useRefが必要ない

SolidJSの場合はReactのuseRefが必要なく、以下のように直感的に書くことができます。

const CompRef = () => {
    let el: HTMLDivElement | undefined = undefined;
    return <div ref={el}></div>;
};

ErrorBoundary/Portal

Reactにもあった便利なコンポーネントがSolidJSでも標準でそろっています。
例えばコンポーネント内のエラーをキャッチしてくれるErrorBoundaryやDOMツリーの外にコンポーネントをレンダリングするときに使えるPortalなどがあります。
これらの機能はSvelteでは存在しなくて困っていいたので非常に助かりました。

その他細かい良い点

  • チュートリアルがしっかりしている
  • ライフサイクル(onMount, onCleanup, onError)
    • この辺はほとんどReactと同じ

SolidJSの改善点

周辺ライブラリが少ない

まだ発展途上のフレームワークではあるので周辺ライブラリは圧倒的に少ないです。
特にアプリケーションを作るうえでは必須とも言っていいUIコンポーネントのライブラリはほとんど存在せず、solid-bootstrapというものだけ見つかりました。
https://solid-libs.github.io/solid-bootstrap/

この辺はライブラリが発展していくとともに充実していくことを期待しています。

props周りがややこしい

props周りでReactでやっていたような扱いをするとSolidJSのReactivityが失われるので注意です。
例えば次のような書き方では意図通りに動きません。

const C: Component<{ loading: boolean }> = (props) => {
    const { loading } = props;
    return <>{loading}</>;
};

ここでは親コンポーネントがloadingというプロパティを変更すれば、子コンポーネントの中身が変更されることを期待しますが、実際にはそうはなりません。
変更されるのはあくまでもpropsだけになっていしまうのでconst { loading } = props;とした時点で、loadingのReactivityは失われます。

Reactivityを保持するには、props.xxxと書くか、関数として定義するかの2択になります。

const C: Component<{ loading: boolean }> = (props) => {
    return <>{props.loading}</>;
};

const C: Component<{ loading: boolean }> = (props) => {
    const loading = () => props.loading;
    return <>{loading()}</>;
};

この辺がややこしいので少し改善されるととっつきやすくなるかと思います。

styleのcamelCaseが出来ない

細かい点ですが、styleを指定する際にfont-sizeのように途中にハイフンが入るような場合、ReactだとfontSizeという風にcamelCaseで指定できたのが、SolidJSだと'font-size'という風にする必要があるのが地味に面倒だったりします。
オブジェクトで指定できるだけSvelteよりはマシなんですが、、、

const C: Component<{}> = (props) => {
    return (
        <div
            style={{
                'font-size': 12
            }}
        />
    );
};

最後に

少し前にSvelteに出会った際にすごい!と思いましたが、だんだん使っていくとReactだとこれができたのになーと思う部分もありました。
SolidJSはReactとSvelteの良いところどりをしているような感じがしたのでこれからもっと使っていこうと思います。

49
20
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?