Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Preact、HooksとSignalsの両立 / Preact: Harmonizing H...

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

TOMIKAWA Sotaro

March 28, 2025
Tweet

More Decks by TOMIKAWA Sotaro

Other Decks in Programming

Transcript

  1. 自己紹介 ssssota { "twitter": "@ssssotaro", "github": "ssssota", "bsky": "@ssssota.bsky.social" }

    株式会社ZOZO フロントエンドテックリード 仕事でReact、 趣味でSvelteやPreactを主に触っている。
  2. Preact ? > Fast 3kB alternative to React with the

    same modern API > Preact(プリアクト)はDOM上に薄い仮想DOMによる抽象化を提供します。 https://preactjs.com/ React互換を謳う軽量な仮想DOMライブラリ。 Reactの進化についていく気がないので互換性については...。 DenoがFreshに採用するなど、死んではいない。 Majorバージョンの更新こそないが、Minorバージョンの更新はある。
  3. Hooks ? いわゆるReact Hooks。 useState, useEffect, useRef, etc… 関数コンポーネントにリアクティビティ(状態や副作用)を導入する道具。 登場当時はクラスコンポーネントが主流だった。しかしクラスインスタンス自体が状態

    を持つことで、ロジックの分離が困難になっていた。 フックによりロジックは再利用性が向上。コンポーネントとはより疎な関係に。 Preactでは、Reactの一部フックが同様に利用できる。
  4. Reactivity with Hooks(VDOM) Code (Preact / React) function Counter() {

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

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

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

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

    Counter() { const [count, setCount] = useState(0); const increment = () => setCount(p => p + 1); return ( <div> {count} <button onClick={increment}>+1</button> </div> ); }
  9. Reactivity with Signals(VDOM) Code (Preact) function Counter() { const count

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

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

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

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

    Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return ( <div> {count.value} <button onClick={increment}>+1</button> </div> ); }
  14. Reactivity 基本方針は、「Signalが更新されたらそのコンポーネントを再レンダリング」。 あくまでも仮想DOMベースのリアクティブシステムなので、 1. 状態を元に仮想DOMの構築 2. 仮想DOMツリーを比較 (diffing / reconciliation) 3.

    実DOMへの反映 (render / commit) が基本。Signalsを使っても同じ規則に従うことで共存できる。 PreactのSignalsは限定的なFine-grained Reactivityが実現されている。 仮想DOMの構築やツリーの比較を行わずにDOMを更新できるので速い。
  15. Fine-grained Reactivity with Signals(VDOM) Code (Preact) function Counter() { const

    count = useSignal(0); const increment = () => { count.value += 1; }; return ( <div> {count /* UPDATED! (before: count.value) */} <button onClick={increment}>+1</button> </div> ); }
  16. Fine-grained Reactivity with Signals(VDOM) VDOM UI 0 [+1] Code function

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

    function Counter() { const count = useSignal(0); const increment = () => { count.value += 1; }; return ( <div> {count} <button onClick={increment}>+1</button> </div> ); }
  18. 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 ( <div> {count} <button onClick={increment}>+1</button> </div> ); }
  19. Limitation of Preact signals JSX内で .value をつけなければFine-grained Reactivityを適用できるが、常 にFine-grained Reactivityが適用できるわけではない。

    例えば、 1. {themeSignal.value === 'light' ? <Light /> : <Dark />} 2. {listSignal.value.map(item => <Item item={item} />)} 条件分岐と反復に関してはFine-grained Reactivityが適用できない =変化時、仮想DOMの再構築が必須 (SolidJSでも <For/> や <Switch/> などで同様の制約を回避)
  20. const countGlobalSignal = signal(0); function Counter() { const countLocalSignal =

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