🦔

Solidjsで作るdependency arrayがない世界

2022/11/27に公開

はじめに

私は、Reactを使用してWebアプリを作っているフロントエンドエンジニアです。
先日この動画を見て、「え、Solidjsすごくね?」と思いました。
なぜなら、

  • コンポーネントは一度しかレンダリングしない(関数は一回しか発火しない)
  • dependency array(useMemo, useEffectとかの第二引数のアレ)がいらない

ためです。

Reactを使用してる方であれば、不要なレンダリングを抑えるためにごにょごにょしたり、dependency array周りでハマったりしたことが一度はあるのではないでしょうか。

それがSolidjsを使用するとなくなるんです。すごくないですか??
動画を見ただけだと半信半疑だったので、本当かどうかTODOアプリを作って検証してみたので、その結果を記事にしてみようと思います。

対象読者

  • Solidjsに興味がある人
  • 無駄なレンダリングを抑制するのに疲れた人
  • dependency array地獄から抜け出したい人

動作環境

本記事で出てくるコードの動作環境は下記のとおりです。

完成品

機能としては下記を搭載してます。

  • 新規登録
  • 一覧表示
  • ステータスの変更
  • ステータスによるフィルター

本記事で紹介するコードはすべて下記リポジトリにおいてあります。
https://github.com/hakushun/dm_solidjs-and-react

Stateの定義

Reactのstateに当たるものをSolidjsでSignalと呼ばれています。

宣言の仕方はご覧の通りほぼ同じです。

1点注意したいのが、createSignalで返却されるのは、gettersetterでどちらも関数です。したがってSignalにアクセスする際はtodo()といったように値を取得します。

React

const [todo, setTodo] = useState<Todo>(initialTodo);
const [todos, setTodos] = useState<Todo[]>([]);
const [filter, setFilter] = useState<Filter>("ALL");

Solidjs

const [todo, setTodo] = createSignal<Todo>(initialTodo);
const [todos, setTodos] = createSignal<Todo[]>([]);
const [filter, setFilter] = createSignal<Filter>("ALL");

変数のメモ化

変数をメモ化するAPIも用意されています。ReactでいうところのuseMemocreateMemoというものです。

こちらも定義の仕方はほぼ同じですが、第2引数に注目です。

冒頭でお伝えしたとおり、Solidjsにはdependency arrayが不要です。

この例の場合だと、createSignalsで定義したtodosに変更があればfilteredTodosが再生成されます。

React

const filteredTodos = useMemo(() => {
    switch (filter) {
      case "ALL":
        return todos;
      case "NEW":
        return todos.filter((todo) => todo.status === "NEW");
      case "DONE":
        return todos.filter((todo) => todo.status === "DONE");
    }
  }, [filter, todos]);

Solidjs

const filteredTodos = createMemo(() => {
    switch (filter()) {
      case "ALL":
        return todos();
      case "NEW":
        return todos().filter((todo) => todo.status === "NEW");
      case "DONE":
        return todos().filter((todo) => todo.status === "DONE");
    }
  });

関数のメモ化

関数のメモ化に関してはSolidjsではAPIはなく、普段どおり関数を宣言するだけで良いみたいです。

React

const handleSubmit = useCallback(
    (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      const generateId = () => Math.random().toString(32).substring(2);
      setTodos((prev) => [...prev, { ...todo, id: generateId() }]);
      setTodo(initialTodo);
    },
    [todo]
  );

Solidjs

const handleSubmit = (e: SubmitEvent) => {
    e.preventDefault();
    const generateId = () => Math.random().toString(32).substring(2);
    setTodos((prev) => [...prev, { ...todo(), id: generateId() }]);
    setTodo(initialTodo);
  };

挙動の違い

ルートのAppコンポーネントにコンソールログを仕込んで、inputを入力したものを比べみました。Reactは入力のたびにログが発火するのに対し、Solidjsは発火しません。

これがコンポーネントが一度しかレンダリングされない、というやつですね。

React

Solidjs

おわりに

いかがでしたでしょうか。

まだ触り始めたばかりでAPIの細かい仕様を把握できてませんが、Reactに近い書き味で実装できました。

実際のプロダクトではコンポーネントを分割したり、外部のAPIからデータを取得してそれを表示したりしますが、そこら辺はまだ試せてないので、引き続き触ってみたいと思います。

Discussion