Slide 1

Slide 1 text

Preact、 HooksとSignalsの両立 Preact: Harmonizing Hooks and Signals #v_tokyo22 / ssssota

Slide 2

Slide 2 text

自己紹介 ssssota { "twitter": "@ssssotaro", "github": "ssssota", "bsky": "@ssssota.bsky.social" } 株式会社ZOZO フロントエンドテックリード 仕事でReact、 趣味でSvelteやPreactを主に触っている。

Slide 3

Slide 3 text

Preact ? > Fast 3kB alternative to React with the same modern API > Preact(プリアクト)はDOM上に薄い仮想DOMによる抽象化を提供します。 https://preactjs.com/ React互換を謳う軽量な仮想DOMライブラリ。 Reactの進化についていく気がないので互換性については...。 DenoがFreshに採用するなど、死んではいない。 Majorバージョンの更新こそないが、Minorバージョンの更新はある。

Slide 4

Slide 4 text

Hooks ? いわゆるReact Hooks。 useState, useEffect, useRef, etc… 関数コンポーネントにリアクティビティ(状態や副作用)を導入する道具。 登場当時はクラスコンポーネントが主流だった。しかしクラスインスタンス自体が状態 を持つことで、ロジックの分離が困難になっていた。 フックによりロジックは再利用性が向上。コンポーネントとはより疎な関係に。 Preactでは、Reactの一部フックが同様に利用できる。

Slide 5

Slide 5 text

Signals ? preact本体とは別パッケージ(@preact/signals)として提供されていつつも、 高度に統合されたSignals実装。 signal, computed, effectなど基本的なAPIから、 関数コンポーネント/カスタムフックから利用するための useSignal, useComputed, useSignalEffectなどが提供されている。 限定的ではあるもののFine-grained Reactivityが実現されている。

Slide 6

Slide 6 text

options Preactにはoptionsというオブジェクトがexportされている。 このオブジェクトにはPreactが内部的に利用するライフサイクルフックが登録され、 上書きすることにより各種挙動の拡張を可能にしている。 @preact/signalsはoptionsのライフサイクルフックを上書きすることで SignalをPreactに統合している。

Slide 7

Slide 7 text

Reactivity with Hooks(VDOM) Code (Preact / React) function Counter() { const [count, setCount] = useState(0); const increment = () => setCount(p => p + 1); return (
{count} +1
); }

Slide 8

Slide 8 text

Reactivity with Hooks(VDOM) VDOM UI 0 [+1] Code function Counter() { const [count, setCount] = useState(0); const increment = () => setCount(p => p + 1); return (
{count} +1
); }

Slide 9

Slide 9 text

Reactivity with Hooks(VDOM) VDOM (ボタン押下でsetCountが呼ばれる) UI 0 [+1] Code function Counter() { const [count, setCount] = useState(0); const increment = () => setCount(p => p + 1); return (
{count} +1
); }

Slide 10

Slide 10 text

Reactivity with Hooks(VDOM) VDOM (VDOMを作り直して比較) UI 0 [+1] Code function Counter() { const [count, setCount] = useState(0); const increment = () => setCount(p => p + 1); return (
{count} +1
); }

Slide 11

Slide 11 text

Reactivity with Hooks(VDOM) VDOM UI (差分を適用) 1 [+1] Code function Counter() { const [count, setCount] = useState(0); const increment = () => setCount(p => p + 1); return (
{count} +1
); }

Slide 12

Slide 12 text

Reactivity with Signals(VDOM) Code (Preact) function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count.value} +1
); }

Slide 13

Slide 13 text

Reactivity with Signals(VDOM) VDOM UI 0 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count.value} +1
); }

Slide 14

Slide 14 text

Reactivity with Signals(VDOM) VDOM (ボタン押下でcountがインクリメント) UI 0 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count.value} +1
); }

Slide 15

Slide 15 text

Reactivity with Signals(VDOM) VDOM (VDOMを作り直して比較) UI 0 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count.value} +1
); }

Slide 16

Slide 16 text

Reactivity with Signals(VDOM) VDOM UI (差分を適用) 1 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count.value} +1
); }

Slide 17

Slide 17 text

Reactivity 基本方針は、「Signalが更新されたらそのコンポーネントを再レンダリング」。 あくまでも仮想DOMベースのリアクティブシステムなので、 1. 状態を元に仮想DOMの構築 2. 仮想DOMツリーを比較 (diffing / reconciliation) 3. 実DOMへの反映 (render / commit) が基本。Signalsを使っても同じ規則に従うことで共存できる。 PreactのSignalsは限定的なFine-grained Reactivityが実現されている。 仮想DOMの構築やツリーの比較を行わずにDOMを更新できるので速い。

Slide 18

Slide 18 text

Fine-grained Reactivity with Signals(VDOM) Code (Preact) function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count /* UPDATED! (before: count.value) */} +1
); }

Slide 19

Slide 19 text

Fine-grained Reactivity with Signals(VDOM) VDOM UI 0 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count} +1
); }

Slide 20

Slide 20 text

Fine-grained Reactivity with Signals(VDOM) VDOM (ボタン押下でcountがインクリメント) UI 0 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count} +1
); }

Slide 21

Slide 21 text

Fine-grained Reactivity with Signals(VDOM) VDOM (count使用箇所更新) UI (count使用箇所更新) 1 [+1] Code function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return (
{count} +1
); }

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

PreactのSignalは通常 .value プロパティによって値を取得・更新できる。 しかし、JSXの各値にSignalをそのまま設定できる。 その部分はFine-grained Reactivityが適用される。

{someSignal /* someSignal.value ではなくていい */}

(someSignal.value = e.currentTarget.value)} /> 限定的...? Fine-grained Reactivity

Slide 24

Slide 24 text

Limitation of Preact signals JSX内で .value をつけなければFine-grained Reactivityを適用できるが、常 にFine-grained Reactivityが適用できるわけではない。 例えば、 1. {themeSignal.value === 'light' ? : } 2. {listSignal.value.map(item => )} 条件分岐と反復に関してはFine-grained Reactivityが適用できない =変化時、仮想DOMの再構築が必須 (SolidJSでも や などで同様の制約を回避)

Slide 25

Slide 25 text

const countGlobalSignal = signal(0); function Counter() { const countLocalSignal = useSignal(0); const [count, setCount] = useState(0); return ...; } もちろん同時に使える。 とはいえ、 useSignal と useState を乱用すると治安が崩壊する。 「useSignal 禁止」など、プロジェクト毎にルールを決めてご利用は計画的に。 Hooks and Signals

Slide 26

Slide 26 text

まとめ PreactにはSignalsがある。 Preact本体とは別パッケージながらoptionsにより統合されている。 Signalsは分岐と反復で制約を受けるもののFine-grained Reactivityを実現、 パフォーマンスに寄与する。 PreactにはHooksもあり、共存できる。状況に応じ使い分けよう。 Signalsのパフォーマンスは良いが、PreactではHooksを圧倒するものではない。 JSXだし、Reactより軽量だし、それなりに速いし、Signalsあるし、 Preactっておもしれーライブラリ。