Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

React CompilerとFine Grained Reactivityと宣言的UIのこれ...

React CompilerとFine Grained Reactivityと宣言的UIのこれから / The next chapter of declarative UI

TOMIKAWA Sotaro

November 22, 2024
Tweet

More Decks by TOMIKAWA Sotaro

Other Decks in Programming

Transcript

  1. きょうは宣言的UIのはなし 宣言的UIはWeb開発の標準となり、Webだけでなくモバイルアプリケーションや デスクトップアプリケーションにも広がりを見せている。 Web: React, Vue.js, Svelte, Preact, etc… モバイル:

    SwiftUI, Jetpack Compose, React Native, Flutter, etc… APIはそれぞれ少しずつ異なるものの、 「状態をもとにUIを宣言する」 という基本的な考え方は共通している。 今日はWeb開発における宣言的UIのこれまでとこれからを考える。
  2. 仮想DOMのしくみ 0. 初回レンダリング時の仮想DOM div h1 p Hello Everyone! Welcome to

    the session. {name: "Everyone"} 1. 状態変化時 仮想DOMを再構築する div h1 p Hello JSConf JP! Welcome to the session. {name: "JSConf JP"} 2. 仮想DOMが構築できたら、差分を検出する (reconciliation / diffing) 3. 検出した差分をもとに、実際のDOMに反映する (render, commit)
  3. Virtual DOM is pure overhead 「仮想DOMは純粋なオーバーヘッドである」 Svelte作者のRich Harris氏が6年前に公開したブログのタイトル。 今後の宣言的UIを考える上での重要なキーワード。 1.

    仮想DOMの差分検出自体コストがかかる 仮想DOMツリーを探索して、効率よく実際のDOMに適用するための差分 を検出する必要がある Reactでは のアルゴリズムを使っているとされる(コスト小) 2. 仮想DOMの構築自体コストがかかる 仮想DOMツリーの構築では様々な計算やアロケーションが何度も発生す る 各種配列、仮想DOM自体のオブジェクト、インライン関数、、、 O(n)
  4. 例:仮想DOMオブジェクトの計算・アロケーションコスト削減 function App({ name }) { return <h1>Hello {name}!</h1>; }

    function App(t0) { const $ = _c(2); const { name } = t0; let t1; if ($[0] !== name) { t1 = <h1>Hello {name}!</h1>; $[0] = name; $[1] = t1; } else { t1 = $[1]; // name が同じ→ 再利用 } return t1; }
  5. 例:関数の計算・アロケーションコスト削減 function List({ items }) { return ( <ul> {items.map((item)

    => { return <li>{item}</li>; })} </ul> ); } function List(t0) { const $ = _c(4); const { items } = t0; let t1; if ($[0] !== items) { t1 = items.map(_temp); // . . . } // ↓ トップレベルへ移動 function _temp(item) { return <li>{item}</li>; }
  6. React Compiler useMemo や React.memo を使えばできなくもないが、 開発者がそれを意識し なければならなかった。 これらのコストをReact Compilerが最適化する。

    1. 仮想DOMオブジェクトをキャッシュする 2. インライン関数をトップレベルに移動する/キャッシュする 状態変化時、通常は状態が変化したコンポーネントの子孫も再構築されるが、 React Compilerでは再構築されるコンポーネントを最小限に抑える。
  7. 仮想DOMのしくみ(with React Compiler) 0. 初回レンダリング時の仮想DOM div h1 p Hello Everyone!

    Welcome to the session. {name: "Everyone"} 1. 状態変化時 仮想DOMを再構築する div h1 p Hello JSConf JP! Welcome to the session. {name: "JSConf JP"} 2. 仮想DOMが構築できたら、差分を検出する (reconciliation / diffing) 3. 検出した差分をもとに、実際のDOMに反映する (render, commit)
  8. 宣言的UIと仮想DOMとReact Compiler ここまで、ReactのReactによるReactのためのReact Compilerを使った Virtual DOM is pure overhead への対応を見てきた。

    これは仮想DOMと 共存する道の1つと言える。 (仮想DOMのVue.js SFCもコンパイルを伴うため最適化が行われている) 一方で、仮想DOMを使わない宣言的UIも存在する。
  9. Signals これらは状態を購読する例。 // React Hooks const [count, setCount] = useState(0);

    useEffect(() => { console.log(count); }, [count]); // Svelte (svelte/store) const count = writable(0); count.subscribe((value) => { console.log(value); }); // Vue.js (@vue/reactivity) const count = ref(0); watchEffect(() => { console.log(count.value); });
  10. SolidJSの弱点 - 制約1 算出プロパティを使う時は関数にする function App() { const [count, setCount]

    = createSignal(0); const double = count() * 2; return ( <> <p>{count()} * 2 = {double}</p> <button onClick={() => setCount((c) => c + 1)}> +1 </button> </> ); }
  11. SolidJSの弱点 - 制約2 早期リターンができない function App() { const [count, setCount]

    = createSignal(0); if (count() === 0) { return <button onClick={() => setCount(1)}>Start!</button>; } return <p>Count is {count()}</p>; }
  12. Svelte 5 と Vue Vapor いずれもFine-Grained Reactivityを実現。(Svelte 5は10月リリース、 Vue VaporはWIP)

    SolidJSとは異なり独自の文法を提供しているため、「SolidJSのような制約を 軽減している」とも言える。 そもそもリターンを書かないから早期リターンもない。 一方で、JSXではない故の問題もある。 最近はRust製の高速なツールチェーンが登場しているが、対応が後回しになりが ち。
  13. SolidJS と Svelte 5 と Vue Vapor いずれも開発者が書いたコードがコンパイルされ、関数コンポーネントになる。 この関数コンポーネントは実際のDOMを返す(Svelteは若干異なるが省略)。 <!--

    コンパイル前 --> <script setup> defineProps(["name"]); </script> <template> <h1> Hello {{ name }}! </h1> </template> // コンパイル後 ( 一部手で調整) const t0 = _template("<h1></h1>") function render(_ctx, $props) { const n0 = t0() _renderEffect(() => { _setText(n0, "Hello ", $props.name, "!") }) return n0 }
  14. Fine-Grained Reactivity Virtual DOM is pure overhead に対する1つの答えが Fine-Grained Reactivity。

    そもそも仮想DOMを使わなければ、仮想DOMのオーバーヘッドはなくなる。 オブジェクトや関数のアロケーションも、関数コンポーネント自体は一度しか呼ばれ ないので問題にならない。 主なプレイヤーはSolidJS、Svelte 5、Vue Vapor。 (Vue Vaporは仮想DOMモードとの併用も可能)