最近新しくギターを購入し、心躍っております。
どうもみつをです。
エンジニア
みつを
今回はなかなかマニアックな技術ネタを引っ提げてきました。
さっそく本題に入りましょう。
エンジニア
みつを
さて、今回はタイトルにもある通り、Signalsについて詳しく見ていこうと思います。
Signalsといえば、新進気鋭フレームワークであるSolidJSやqwikで採用されている技術ですが、1ヶ月ほど前にこのSignalsの話題でフロント界隈が少しざわついていたように見えたので、気になって色々と調べてみました。
今回はそこで知ったことをまとめた記事になっています。
目次
そもそもSignalsとは何なのか
Signalsは状態を保持するための手段です。
基本的にはReactにおけるStateと同じような概念になります。
ただしこの二つにはしっかりと違いがあつので、この記事ではその違いを見ながらSignalsの特徴を抑えていきます。
useStateとの違い
useStateとuseSignalの返す値を見てみる
それぞれ簡単なカウンターアプリを例に違いを見ていきます。
まずはuseStateを使ったReactのコードです。
import { useState } from "react";
export const Counter: React.FC = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
};
countというstateの値とsetCountというcountを更新するための関数が返っていますね。
続いてSolidJSにおけるSignalsを見ていきます。
※SolidJSでSignalsを扱う場合は、createSignalというAPIを使います。
import { Component, createSignal } from "solid-js"'
export const Counter: Component = () => {
const [count, setCount] = createSignal(0)
return (
<button onClick={() => setCount(count() + 1)}>
{count()}
</button>
);
};
useStateと違い、countが関数になっているのが分かります。
ここでは、useStateが返すのはvalueとsetterであり、createSignalが返すのはgetterとsetterであることがわかります。
言い換えると、useStateは値の実体を返しており、useSignalは値への参照を返しています。これが重要です。
せっかくなので、同じくSignalsを扱うフレームワークのqwikの例も見てみましょう。
import { component$, useSignal } from "@builder.io/qwik";
export const Counter = component$(() => {
const count = useSignal(0);
return (
<button onClick$={() => count.value++}>
{count.value}
</button>
);
});
qwikの場合は、countが「value」というプロパティを持ったオブジェクトになっているのと、countを更新するときはそのcount.valueをそのまま操作しているのがわかります。
使い方の違いはありますが、このvalueプロパティはSolidJSにおけるgetter/setterの立ち位置となっており、こちらも値の実体ではなく値への参照を返しています。
レンダリングの挙動を見てみる
各フレームワーク/ライブラリ毎に、カウンターボタンを押してsetterを実行した時のレンダリングの挙動を見てみます。
それぞれコンポーネントのレンダリング時にログで「rendered」と出力するようにしています。
まずはReactのuseStateから。
3回ボタンを押して見ました。
ログを見てみると、3回「rendered」と出力されています(ボタンを押す直前にログを一度クリアしています)。
ボタンを押し、setterで値が更新されるたびにコンポーネントがレンダリングされています。
続いてSignals。
まずはSolidJSから見てみます。
3回押してみましたが「rendered」と出力されないので、コンポーネントは再レンダリングされていないようです。
qwikもSolidJSと同じ結果でした。
結果的に、Stateは更新時にコンポーネント全体を再レンダリングするのに対し、Signalsは更新してもコンポーネント自体は再レンダリングされず、Signalsの値を表示しているテキストのnodeだけが更新されています。
なぜこんな挙動に?
一言でまとめると、「useStateによって返される値は非リアクティブ、useSignalによって返される値への参照はリアクティブであるから」といえます。
リアクティブであるということは、値が更新されたことを検知して、その値を呼び出しているところにその更新を知らせる必要があります。Signals(値への参照)は、どこでそれが呼び出されたかを監視し、呼び出されている箇所に値の更新を知らせてくれるという、subscriptionの動きをします。
よってSignalsはコンポーネント全体を再レンダリングする必要がなく、呼び出された部分だけを更新するだけでいいということになります。
このように、Signalsは最初からレンダリング回数が抑えられているため、再レンダリングのコストについて頭を抱えることも少なくなるんですね(逆に再レンダリングして欲しいのにしてくれないみたいな悩みもあるようです)。
いろんなフレームワークで採用されているSignals
このSignals(リアクティブな状態管理)はいくつかのフレームワークで採用されています。
先程SolidJSとqwikを例に出しましたが、最近PreactやAngularでもSignalsを採用する動きがありました(他にもあるかも)。
また、Vue.jsのshallowRefというAPIも「Signals」という名前は使っていないものの、かなり似た設計になっているようです。
よくよく考えると、shallowRefに限らず、よく使われるrefも挙動的にはSignalsっぽさがありますよね。
筆者の意見を少し
あくまでも主観ですが、JSフレームワーク界隈(?)全体的にSignalsを採用する流れがある印象です。
ただ、Reactは依然として変わらないあたり、なんとなくMetaらしさを感じます。
この記事の内容的に、かなりSignalsを推しているように思われるかもしれませんが、
そもそも再レンダリングのコストはそこまで気にすべきでないといった主張があることからも、
useState、useSignalを比較する上で、どちらが優れているといった議論はあまり意味を成さないのではないかと思います。
さいごに
ここいらで少し宣伝を…
4月もロジカルスタジオで勉強会が開催されます!!!
日時:2023/04/14(金) 19:00 ~ 21:00
場所:株式会社ロジカルスタジオ
テーマ:WEBフロントエンドのお話 TypeScript etc…
詳細:https://coroutine.connpass.com/event/279026/
金曜の夜、さくっと勉強して帰りませんか…?
たくさんのご参加お待ちしております!!