翻訳:Rich Harris「形而上学とJavaScript」に関する見解(ReactによるDOMの抽象化の不完全性について)

この記事は「Thoughts on Rich Harris’ “Metaphysics and JavaScript”」の日本語訳です。Svelteのコーディングスタイルや記事として言及している講演のスライド併せて参照しなければ理解しづらい内容です

公開にあたっては著者のJim Nielsen氏に許諾をいただいています。

要旨としては、ユーザーにとってSvelteは実際のDOMとのメンタルモデルのギャップが少なく感じられるデザインであって、結果的に、Reactより理解しやすいプログラミングモデルになっているという話です。


Svelteの考案者であるRich Harris氏は、Reactのプログラミングの側面を批判する「形而上学とJavaScript」というタイトルの最近の講演のスライド共有しました。この講演には非常に説得力があり、Reactを利用した私の経験にも当てはまるとわかりました。

この記事では彼の論点のいくつかを反芻して、Reactでのプログラミングに関する私自身の感情をさらに解明し、明確にするために役立てたいと思います。

はじめに:私はReactが大好きで毎日使っています。以降の話はそれを変えてしまうものではありません。

UIは状態からなる関数であるか?

Richは講演の冒頭で、「神聖な牛の中でもっとも神聖なもの」(訳注:ヒンドゥー教では牛は神聖な存在であり、原文のsacred cowsは神聖なるものという慣用句)を批判する計画であることを明らかにしました。UIはアプリケーションの状態からなる関数であるということについてです。

UI = F(STATE)

この公式化こそがReactのプログラミングモデルの核心ですが、しかしこれが間違いであると主張するつもりはありません。私はこれが不完全であると主張します。Reactアプリを作ったときに実際になにが起こっているかの説明というよりは、イデオロギー的な主張であるということです。そしてこれは私たちがコードについてどう考えるかについて、より深い真実を曖昧にしてしまう思考様式を表していると私は主張します。

Gut punch.

では公式UI=F(STATE)はどのように間違っているのでしょうか? ああ、でも彼はそれが「間違い」だとは言わずに「不完全」だと言いました。彼は「UIは状態からなる関数である」というのはReactアプリを作ったときになにが起こっているかを正確に説明するものではないと言います。

私はRich氏の言葉を引用していますが、彼が言ったのは、UIがアプリケーションの状態からなる関数であるという考え方は抽象化であり、うまく機能してかなりあなたを助けますが、それでもただの抽象化だということ。抽象化は便利ですが、それでも素材の上にあるレイヤーであることに変わりはありません。下層の素材の上をきれいに塗装できないとわかったときには非常に厄介になることがあります。抽象化の欠点を克服するための一貫した戦術を見つけられなければ、火事が起こり、フラストレーションで燃え尽きるまで絶えず軋轢が生じます。

Julio Biason氏が書いた記事「私が苦労しながら学んだ物事(ソフトウェア開発の30年で)」を思い出します。彼は、デザインパターンは解決策を見つけるためではなく、解決策を説明するために使うべきであると述べています。

(繰り返しますが個人的な意見では)デザインパターンが適用されているのを見ると、ほとんどの場合、解決策を見つけるための方法として適用されていました。そのため最終的には解決策をひねって、時には問題の方をパターンに適合させることになるでしょう。

彼は特に「デザインパターン」について語っていますが、UIは状態からなる関数であるというイデオロギーをめぐるRich氏の主張にもかなりよく当てはまるようです。言い換えれば、UIは状態からなる関数であるという考え方は非常に有用であることがわかります。複雑さを乗り切るのに役立ちます。しかしながらそれは、現実の世界でウェブアプリケーションを実際に構築する方法、つまりDOMという基礎の上にあるイデオロギー的な抽象概念です。Rich氏はこれについて時間をかけて説明します。

「UI = F(STATE)」はイデオロギー的な声明だと思いますが、私が言いたいのは、資本主義イデオロギー共産主義イデオロギーのような他のイデオロギーと同じように、個人やグループが純粋に認識論的な理由以外で保持している規範的な信念や価値観の集合であり、大まかに言うと、「人々が実際に生きている世界ではなく、人々が想像したいと思っている世界を表している」ということです。

私は「イデオロギー」という言葉を、否定的なものではなく記述的ラベルとして使用しています。

しかしイデオロギーは危険を伴う場合があります。なぜなら彼らがいつもしているように、現実に逆らって苛立ち始めると、イデオロギーの衝動は常に、理論を修正することよりも、現実を作り直すことにあるからです。

「UIは状態からなる関数」というのは複雑なウェブアプリケーションを構築するための適切な解決策に近い理想論であり、私たちはReactのプログラミングモデルを通じてそのアイデアを活用しています。ですが今日では、私たちが実際に構築する素材(DOM)に合うように、イデオロギー的な解決策に多くのものを加えなければならないという不可避的な欠陥があります。どちらかというと、むしろトレードオフになると言うべきかもしれません。それは、私たちが生きているイデオロギーが現実のミスマッチと摩擦するときに軋轢を生みます。

私が本当に称賛したいのは、フレームワークの背後にあるイデオロギーと、ウェブアプリケーション構築の現実とのミスマッチに対処しようとするRich氏の試みです。素晴らしいことです。これはReact対Vueのような話をするときに聞きたいものです。単なる「ReactはJSXを使い、VueはHTMLテンプレートを使っている」という話でなく。

話が脱線しました。Rich氏が中断したところから続けましょう。

では関数型UIに対して非常に素朴なアプローチを取ったとしたらどうなるでしょう。たとえばある状態からなにかしらのUIを生成する関数を作成します。これには、その状態が変化したときに関数を再び呼び出すイベントリスナーが含まれます。

Rich氏の講演より、状態からなる関数としてのUIの簡単な例を描いたアニメーションGIF

const render = state => {
  document.body.innerHTML = `
    <h1>Hello ${state.name}</h1>
    <input value="${state.name}">
  `;

  const input = document.querySelector('input');

  input.oninput = () => {
    render({
      name: input.value
    });
  };
};

render({ name: 'world' });

ここでなにが起こっているのかがわかりますか? 「状態からなる関数としてのUI」というアイデアを示す非常に単純でインタラクティブな体験を実装しようとしています。「Hello XXX」というテキストがありますが、ここではXXXは<input>の値で埋められます。JavaScriptのコードはinputの変更を監視するだけに過ぎず、変更が行われるたびにDOMにアクセスして<body>のすべてを消去し、inputの新しいステートフルな値に基づいてUIを再描画します。

この例は、UIはアプリケーションの状態からなる関数であるという考え方をよく表しています。ではここでの問題はなんでしょうか。なぜこれが機能しないのでしょうか? UIには、nameというアプリケーションの状態だけでなく、より多くの状態があります。Rich氏は言う。

古いinputにはフォーカスがあり、新しいinputにはフォーカスがないため、機能しないことがわかります。

コードが実行されるたびにDOMにアクセスし、<body>のすべてを新しいHTMLに置き換えます。しかしおそらく気がついていなかったのは、DOMにはすでになにかしらの状態、つまりinputにフォーカスがあるということです。新しい状態で再描画するために<body>のすべてを削除すると、特定のinputにフォーカスがあるというDOMにあらかじめ含まれている暗黙の状態が失われます。Reactは制御された(controlled)inputとともにこの問題を処理(解決)してくれますが、もしかするとあなたはご存知なかったでしょうか? Rich氏の指摘に沿って簡単な例を作るまで、私はそれを完全には理解していませんでした。「クソッ、ブラウザがデフォルトでどの状態を処理するか把握していなかった」。したがってUIは必ずしもアプリケーションの状態からなる関数ではないようです。

Rich氏は、DOMの暗黙的な状態をアプリケーションのコードに引き上げることでこの問題を解決できることを示しています。なにが起こるのでしょう? 特にアプリとそのインタラクションが複雑になるにつれて、同じ種類の問題が次々と発生します。

つまりDOMはステートフルであることがわかります。しかし私たちがReactのようなフレームワークから逃れようとしているのはそれだと思いました。そしてRich氏が指摘しているミスマッチがあります。フレームワークには必ずしも現実と一致しないイデオロギー的な傾向があります。このようにして、私たちは現実に合わせてイデオロギーを再構築するのではなく、イデオロギーに合わせて現実を再構築しようとすることがよくあるのです。

Rich氏が続けて指摘しているように、DOMの暗黙の状態(inputのフォーカス、要素がマウントされた時間など)は、ユーザー(開発者)が画面になにを表示するかを決定する上でいつでも重要な要素です。この暗黙の状態の多くは簡単にはアプリケーションに持ち込めません。

純粋な関数を使ってUIを表現したいという願望はDOMの本質と真っ向から対立しています。状態→ピクセル変換を記述するには最適な方法であり、ゲームレンダリングやジェネレーティブアートには最適です。しかしウェブ上でアプリを構築していると、そのアイデアはステートフルなメディアの実際に逆らってしまいます。

あるレベルでは、ReactはDOMの多くの面倒を見てくれていることをご存知でしょう。知っています。しかし「UI=F(STATE)」という基本的なアイデアを取り入れたアプリをJavaScriptで(Rich氏が提供した例のように)構築しようとしたとき、Reactが私のためにどれだけのことをしているのかが分かり始めました。

そしてそれが、私がRich氏の議論の頂点として見いだしたものへと導いてくれるのです。

もちろんReactが「DOMの暗黙的な状態の競合」をどのように処理するかはわかっています。Reactは仮想DOMツリーの新しいノードをDOMの既存のノードにマップします。つまり、Reactは明らかに機能していない基盤を機能的に抽象化したものです。

私の経験では、抽象化と抽象化の間のギャップが大きければ大きいほど、プログラマーが「インピーダンスミスマッチ」と呼ぶものに悩まされる可能性が高く、Reactでもそれを経験しているように思うのです。

つまりもっとも基本的なレベルでは、Reactのプログラミングモデルとウェブの命令型APIとの間にミスマッチがあります。ウェブには性質があり、Rich氏は「UIは状態からなる関数」という考えがそれに反することをうまく説明しているように思います。

ではここからどこへ向かうのでしょうか。私にはわかりません。しかしこれを書くことで、私が頻繁に感じる摩擦をよりよく理解できるようになりました。そして「Svelteが存在する理由」という講演のための完璧な準備です。