698
418

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大好き侍が、「もうSolidJSでいいじゃん...//」ってなったワケ。

Last updated at Posted at 2022-05-30

Reactが好きです。

Reactが好きです。コンポーネントを関数として扱うのが好きです。
SolidJSはReactそっくりの書き心地(DX)を保ちつつ、Reactに足りない要素を兼ね備えた期待の新人です。

コードの比較

React

const Counter = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log(`Count: ${count}`)
  }, [count])

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(prev => prev + 1)}>Add</button>
    </div>
  )
}

SolidJS

const Counter = () => {
  const [count, setCount] = createSignal(0)

  createEffect(() => {
    console.log(`Count: ${count()}`)
  }) //第2引数はありません。SolidJSが勝手に依存している状態を感知してくれます。

  return (
    <div>
      <div>{count()}</div>
      <button onClick={() => setCount(prev => prev + 1)}>Add</button>
    </div>
  )
}

そっくりですね。Reactを書いたことがある方なら容易に読めるはずです。

SolidJS最大のメリット

パフォーマンスです。もうばちくそ速いです。下のグラフによるとReactより約2倍も速いです。1なんならほぼVannilaJSと同じくらい速いです。
パフォーマンス

なにが違うのか

記述はそっくりなのになぜこんなにもパフォーマンスに差が出るのか。それは単純に、SolidJSとReactは根本的に別物だからです。具体的な違いを幾つかあげます。

仮想DOMがない

Reactといえば仮想DOMみたいなとこあるのでもう完全に別物ですね。SolidJSはjsxを本物のDOMにコンパイルしています。
状態の更新の副作用としてDOMを書き換えているらしいです。2

コンポーネント(関数)が一度しか呼ばれない

Reactのコンポーネント内に直でconsole.logを書くと、状態が更新されるたびに実行されます。SolidJSのコンポーネント内に同じくconsole.logを書いても、最初に関数が呼ばれる時しか実行されません。
これはなかなか大きな違いです。Reactデベロッパーは日々、「不必要な再レンダリングを防ぐ」という課題に頭を悩ませていますが、SolidJSにおいてはそもそも「再レンダリング」は発生しないのです。

SolidJSだから、できたこと。

こういった根本的な違いにより、SolidJSにのみ可能な芸当が存在します。

createSignalでやりたい放題

createSignalは、useStateとは違いHookではありません。React内に存在するHookのルールは適応されないのです。
コンポーネントの外に書くこともできますし、普通の関数内に書いたり、状況に応じて増やしたりもできます。

//コンポーネントの外に書いても普通に動作します。
const [count, setCount] = createSignal(0)

const Counter = () => {
  return (
    <div>
      <div>{count()}</div>
      <button onClick={() => setCount(prev => prev + 1)}>Add</button>
    </div>
  )
}
const Counter = () => {
 //コンポーネント内の関数でも呼べます。この例だと、コンポーネント内に直接customHookを書いているような感じです。
  const createCounter = () => {
    const [count, setCount] = createSignal(0)
    return { count, setCount }
  }

  const counterOne = createCounter()
  const counterTwo = createCounter()

  return (
    <div>
      <div>{counterOne.count()}</div>
      <button onClick={() => counterOne.setCount(prev => prev + 1)}>Add</button>

      <div>{counterTwo.count()}</div>
      <button onClick={() => counterTwo.setCount(prev => prev + 1)}>Add</button>
    </div>
  )
}
const Counter = () => {
  const [counters, setCounters] = createSignal([])

  //ボタンがクリックされるたびにカウンターが増えます。
  const addCounter = () => {
    const [count, setCount] = createSignal(0)
    setCounters(prev => [...prev, { count, setCount }])
  }


  return (
    <div>
      <For each={counters()}>
        {counter => (
          <div>
            <div>{counter.count()}</div>
            <button onClick={() => counter.setCount(prev => prev + 1)}>
              Add
            </button>
          </div>
        )}
      </For>
      <button onClick={addCounter}>Add Counter</button>
    </div>
  )
}

context状態管理ライブラリもいらない

察しのいい方はお気づきでしょうが、createSignalがどこにでも書けるなら上記の2つは必要ありません。signals.tsのようなファイルに、グローバルな状態を保管すればよいのです。

import { createSignal } from 'solid-js'

export const [count, setCount] = createSignal(0)
import { count, setCount } from '../signals'

const Counter = () => {
  return (
    <div>
      <div>
        <div>{count()}</div>
        <button onClick={() => setCount(prev => prev + 1)}>Add</button>
      </div>
    </div>
  )
}

拍子抜けな程にシンプルに、グローバルな状態の管理ができますね。こういうのでいいんだよ。

6/10更新:
createMemoなどの計算を含むグローバルな状態に関しては、createContextcreateRootを使用します。(詳しくはこちら)
createSignalは、Contextなどではオーバーキルになるようなシンプルな状態を管理するのに適しています。
なお、ssrを使用する場はcreateContextを使用します。

直でsetInterval

Reactの場合、こんな書き方をしたら再レンダーされるたびに新しくsetIntervalが登録されるので大変なことになります。一方SolidJSでは関数が一度しか呼ばれないので、トップレベルにsetIntervalを書いても問題ありません。

const Counter = () => {
  const [count, setCount] = createSignal(0)

  const id = setInterval(() => {
    setCount(prev => prev + 1)
  }, 1000)

  onCleanup(() => {
    clearInterval(id)
  })

  return (
    <div>
      <div>{count()}</div>
      <button onClick={() => setCount(prev => prev + 1)}>Add</button>
    </div>
  )
}

もうSolidJSでよくない?

まだReactが勝る部分はあるんですよ。Next.jsの存在とか、大きなコミュニティとか。
とはいっても、SolidJS版Next.jsは開発中とのことですし、コミュニティに関してもこれから育てていけばいいわけで。実際に海外では少しづつ注目を浴び始めていますしね3
かくいう自分も、日本におけるSolidJSの認知度を高められたらいいなと思いこの記事を書きました。

「ぼくがかんがえたさいきょうのReact」がこのSolidJSです。ぜひお試しください。

  1. 公式サイトより引用。

  2. 仕組みの詳しい解説

  3. State of JS 2021の満足度部門にて、svelteと並び1位

698
418
8

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
698
418

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?