Skip to content

Latest commit

 

History

History
666 lines (458 loc) · 16.6 KB

File metadata and controls

666 lines (458 loc) · 16.6 KB
theme title info drawings mdc overviewSnapshots fonts
ohkime
React CompilerとFine-Grained Reactivityと宣言的UIのこれから
JSConfJP 2024
persist
true
true
sans serif weights
BIZ UDPGothic, Zen Kaku Gothic New
BIZ UDPMincho
200,400,700

React Compilerと
Fine-Grained Reactivityと
宣言的UIのこれから

JSConf JP 2024 - TOMIKAWA Sotaro (ssssota)


はじめまして!

冨川(TOMIKAWA)宗太郎(Sotaro)

株式会社ZOZO フロントエンドエンジニア(テックリード)

{
  "x": "ssssotaro",
  "bsky": "ssssota.bsky.social",
  "github": "ssssota"
}

仕事ではReact、趣味はSvelteかPreactを使っていることが多いです。

<style> ruby { font-size: 3rem; } rt { font-size: 1rem; } img { position: absolute; right: 5rem; top: 5rem; width: 10rem; height: 10rem; } </style>

きょうは宣言的UIのはなし

宣言的UIはWeb開発の標準となり、Webだけでなくモバイルアプリケーションやデスクトップアプリケーションにも広がりを見せている。

  • Web: React, Vue.js, Svelte, Preact, etc...
  • モバイル: SwiftUI, Jetpack Compose, React Native, Flutter, etc...

APIはそれぞれ少しずつ異なるものの、
「状態をもとにUIを宣言する」 という基本的な考え方は共通している。

今日はWeb開発における宣言的UIのこれまでとこれからを考える。


layout: cover

1章 宣言的UIと仮想DOM


宣言的UIと仮想DOM

10年前、“仮想DOMという概念が俺達の魂を震えさせ”ていた。

(仮想DOMは宣言的UIを実現するための手段で宣言的UIそのものではないが)
宣言的UIをここまで広めたのはReactやVue.jsのような仮想DOMを使ったライブラリの存在といっても過言ではない。

宣言的UIのこれからを語る上で、仮想DOMをまずは振り返る。


仮想DOMのしくみ

function App({ name }) {
  return (
    <div>
      <h1>Hello {name}!</h1>
      <p>Welcome to the session.</p>
    </div>
  );
}

例えばこんなコンポーネントを考える。


仮想DOMのしくみ

0. 初回レンダリング時の仮想DOM

1. 状態変化時 仮想DOMを再構築する

2. 仮想DOMが構築できたら、差分を検出する (reconciliation / diffing)

3. 検出した差分をもとに、実際のDOMに反映する (render, commit)


Virtual DOM is pure overhead

「仮想DOMは純粋なオーバーヘッドである」
Svelte作者のRich Harris氏が6年前に公開したブログのタイトル。
今後の宣言的UIを考える上での重要なキーワード。

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

React Compiler

今年春発表されたReact Compiler (React Forgetは2021年発表)。
これが、先の問題を解決する。

  1. 仮想DOMの構築自体コストがかかる
    • 仮想DOMツリーの構築では様々な計算やアロケーションが何度も発生する
      • 各種配列、仮想DOM自体のオブジェクト、インライン関数、、、

様々な計算やアロケーションとは?どのように解決するのか?


例:仮想DOMオブジェクトの計算・アロケーションコスト削減


例:関数の計算・アロケーションコスト削減


React Compiler

useMemoReact.memo を使えばできなくもないが、 開発者がそれを意識しなければならなかった。

これらのコストをReact Compilerが最適化する。

  1. 仮想DOMオブジェクトをキャッシュする
  2. インライン関数をトップレベルに移動する/キャッシュする

状態変化時、通常は状態が変化したコンポーネントの子孫も再構築されるが、
React Compilerでは再構築されるコンポーネントを最小限に抑える。


仮想DOMのしくみ(with React Compiler)

0. 初回レンダリング時の仮想DOM

1. 状態変化時 仮想DOMを再構築する

2. 仮想DOMが構築できたら、差分を検出する (reconciliation / diffing)

3. 検出した差分をもとに、実際のDOMに反映する (render, commit)


宣言的UIと仮想DOMとReact Compiler

ここまで、ReactのReactによるReactのためのReact Compilerを使った Virtual DOM is pure overhead への対応を見てきた。 これは仮想DOMと共存する道の1つと言える。

(仮想DOMのVue.js SFCもコンパイルを伴うため最適化が行われている)

一方で、仮想DOMを使わない宣言的UIも存在する。


閑話休題 宣言的UIとJSX

いまでは様々なフレームワークが利用しているJSX。
当初はFacebook(現Meta)がReactのために開発した言語拡張。

JSXはReactとともに普及し、
現在ではマークアップのデファクトスタンダードとなっている。

独自文法とは異なり周辺ツールチェーンの恩恵を受けやすいのも大きなメリット。

  • Parser
  • Linter / Formatter
  • Transformer
  • etc...

layout: cover

2章 Fine-Grained Reactivity


SolidJS

3年前、SolidJS(v1)が登場。Reactと同じくJSXを採用しながら、仮想DOMを使わない宣言的UIを実現。

状態は全てSignalsで管理。Signalsの値が変化すると、それを検知して該当のSignalsが使われたDOMを更新。

これがいわゆるFine-Grained Reactivity

Fine-grained: きめの細かい


Signals

Fine-Grained ReactivityのベースにあるのがSignals。SignalsはStreamやObservableのような概念で、単一の値を持ちその値が変化すると通知できる。

StreamやObservableではなく、Signalsが宣言的UIで重宝されるのはインターフェースと柔軟性のバランスが取れているため。


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);
});

SolidJSの弱点

SolidJSのコンポーネントは1度しか実行されない。

1度実行でコンポーネントのすべての状態を返す必要がある(イメージ)。

また、それゆえの制約がある。


SolidJSの弱点 - 制約1

算出プロパティを使う時は関数にする

```jsx
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>
    </>
  );
}
```

```jsx
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>
    </>
  );
}
```

SolidJSの弱点 - 制約2

早期リターンができない

```jsx
function App() {
  const [count, setCount] = createSignal(0);
  if (count() === 0) {
    return <button onClick={() => setCount(1)}>Start!</button>;
  }
  return <p>Count is {count()}</p>;
}
```

```jsx
function App() {
  const [count, setCount] = createSignal(0);
  return (
    <Show when={count() === 0} fallback={<p>Count is {count()}</p>}>
      <button onClick={() => setCount(1)}>Start!</button>
    </Show>
  );
}
```

Svelte 5 と Vue Vapor

いずれもFine-Grained Reactivityを実現。(Svelte 5は10月リリース、Vue VaporはWIP)

SolidJSとは異なり独自の文法を提供しているため、「SolidJSのような制約を軽減している」とも言える。 そもそもリターンを書かないから早期リターンもない。

一方で、JSXではない故の問題もある。
最近はRust製の高速なツールチェーンが登場しているが、対応が後回しになりがち。


SolidJS と Svelte 5 と Vue Vapor

いずれも開発者が書いたコードがコンパイルされ、関数コンポーネントになる。
この関数コンポーネントは実際のDOMを返す(Svelteは若干異なるが省略)。


Fine-Grained Reactivity

Virtual DOM is pure overhead に対する1つの答えが
Fine-Grained Reactivity

そもそも仮想DOMを使わなければ、仮想DOMのオーバーヘッドはなくなる。

オブジェクトや関数のアロケーションも、関数コンポーネント自体は一度しか呼ばれないので問題にならない。

主なプレイヤーはSolidJSSvelte 5Vue Vapor
(Vue Vaporは仮想DOMモードとの併用も可能)


閑話休題 Signals

Fine-Grained Reactivityの基本はSignals。
このSignal、各フレームワークが独自に実装している。当然互換性はない。

そこで、TC39のプロポーザルとしてSignals標準化の動きがある。(Stage 1)

現状すぐに使えるわけではないが、標準化されれば各Fine-Grained Reactivity系フレームワークの互換性が向上する可能性もある。 (パフォーマンスはそれほど変わらない)


2.7章 いまとこれから

仮想DOMが宣言的UIを広めてきたが、仮想DOMを使わない宣言的UIも勢力を拡大している。

Reactは仮想DOM由来の問題を解決するためにReact Compilerを開発中。
React Compilerは名前の通りReact(のコンポーネント)をコンパイルする。

他のライブラリはSignalを使ったFine-Grained Reactivityがアツい。 Fine-Grained Reactivityでは仮想DOMを使わず、状態に追従する実際のDOM要素を作り出す。


これからの宣言的UIに必要な要素

いまを考えると、これからの宣言的UIには次のような要素が求められると考えられる。

  1. パフォーマンス
    • 命令型のコード(=フレームワークなし)に漸近するスピード
  2. 開発体験
    • 開発者が違和感のないコードを書けること
    • 外部ツールとの親和性
  3. 互換性
    • 既存コードとの親和性

宣言的UIと仮想DOMとFine-Grained Reactivity

宣言的UIはWeb開発の標準となった。

React CompilerやFine-Grained Reactivityは、
これからの宣言的UIのキモになるかもしれない。

一方で、仮想DOMが極端に遅いわけではない
我々は現実的なスピードで動作するWebアプリを作っているし、使えている。

新しい技術を注視しつつ、いまある仮想DOMなどの技術と課題を見つめていけばよい。


まとめ

  • 宣言的UIはWebのものではなく、広く使われるようになっている
  • 仮想DOMは宣言的UIを広めたが、オーバーヘッドも問題視されている
  • React CompilerはReactの仮想DOMオーバーヘッドを解決する
  • Fine-Grained Reactivityは仮想DOMを使わず、宣言的UIを実現する
  • これからの宣言的UIには高いパフォーマンス・開発体験・互換性が求められる
  • 仮想DOMも死なないので引き続き魂を震わせてOK