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
などの計算を含むグローバルな状態に関しては、createContext
やcreateRoot
を使用します。(詳しくはこちら)
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です。ぜひお試しください。