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

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

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

Avatar for TOMIKAWA Sotaro

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モードとの併用も可能)