JSXが好きな理由

度々このブログでも私はReactやJSXに対する所感を残しているので、今回改めてJSXについて考えてみようと思った。 なぜ私がJSXが好きなのかという理由について改めて考えてみようと思う。

結論から先に書いてしまうと、よりJSXは自然に見えることが私がJSXを好きな理由だと思う。

もともとHTMLがここまで普及することは想像していなかった。 デスクトップアプリケーションという領域ではもちろんVisualStudioやXcodeを使わなければいけないのだけれども、ブラウザの拡張機能やElectron、React Nativeを始めとするHTMLでアプリケーションが作れてしまうのは本来の目的から外れているようだけれども立派なアプリケーションであるから不思議である。

Hyper Text Markup Languageという意味合いからすると、やはりHTMLの初出は文章であるし、見出しやら改行やらそういった目的のタグがあるのは理解できる。 divという領域を意味を持つタグに連なりを持った表現(Cascading Style)を重ねているわけである。 それに加えてなぜかforminputというタグが存在している。 現在ではbuttonというタグが定義されて久しいけれども、初期の頃は<input type="submit">としなければボタンとして使えなかった。 これらの存在がなければきっとHTMLは今以上に重要な役割を持つことはなかったと思う。

FlashやJavaのアプレットを使わずにWindowsやMacの垣根を超えてどこでも使えるという意味ではやはりHTMLが、WebブラウザがWebアプリケーションとして進化したのはよかったことだと思う。 この投稿を書き始めた時点では忘れていたが、あの当時は私自身もそれを望んでいたはずだ。

単にアプリケーションという意味合いであれば表現力という意味でもXMLがもっとブラウザに浸透すればよかったのかもしれないが、さすがに自由すぎてブラウザで扱うには難しかったのかもしれない。 その証拠かどうかはわからないが、単純なRSSリーダーですらブラウザには搭載されていないし、今日当たり前のように使えるようになるまではそれなりに長く感じた。 余談ではあるがRaphaëlというライブラリがあったのだが、そういったライブラリを使ってようやく表示できるものだった気がする。

文章を表現するのであればHTMLは成り行きとして自然ではあるが、ことアプリケーションとして捉えると歴史的な制約がHTMLには重くのしかかっている。

例えばすぐに思い浮かぶ例であればHTMLではGETPOSTしか存在していない。 私はRuby on RailsやSinatraでその概念に親しんだけれども、CRUDの初出は1980年代まで遡るようだ。 HTMLという概念を超えてこれだけCRUDがマッチするとは誰しも思わなかったであろう。 しかしHTML5がこれだけ普及した現在でもなぜだか<form method="PUT">が使えないのは不思議であるし、PUTPATCHの違いは厳密にはあるもののCRUDの表現上ではあまり意味をなさないような違いがある。 HTMLというよりもHTTPサーバーにまで話が言及してしまうのだけれども、これは持ちつ持たれつではあるので容赦してほしい。

HTMLを再定義するという意味合いでは最近になってHTMXというプロジェクトが出てきたのがセンセーショナルではあったけれども、それ以外にもCustom Elementsが出てきたのは当然だったのかもしれない。 それくらい従来のHTMLでは文章を定義するという意味以外に使われているし、改良を加えたいと思う気持ちが強くなるのであろう。

実際に私がこの文章をふと書きたくなったきっかけはなんの変哲もないERBである:

<div class="container">
  <% cache @entries do %>
    <% @entries.each do |entry| %>
      <div class="col-md-3">
        <div class="card">
          <%= render "card_img_top", entry: entry %>
          <div class="card-body">
            <p class="card-text">blah</p>
          </div>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

こういったコードを嫌というほど見続けてきていて私は正直辟易としている。 エディタの補完機能を使ったりはするものの、自分で書いているコードである。 いわゆるIDEを使わずとも我々はこういったコードを何もない画面からまるで手品のように画面に書き出すことができる。 その楽しさを実現しているのがHTMLではあるのだけれども、それでもこのコードはあまりにもノイズが多いような気がする。

このHTMLをそのままJSXで表現するとこうなる:

function Container({ entries }) {
  return (
    <div className="container">
      <EntryCache>
        {entries.map(entry => (
          <Entry>
            <Card>
              <CardImgTop entry={entry} />
                <CardBody>
                <Typography text='blah' />
              </CardBody>
            </Card>
          </Entry>
        ))}
      </EntryCache>
    </div>
  )
}

おそらくこのコードだけ見てもまだ頭の中には疑問符が残る。

しかしJSXであるならば最終的にはこのような表現にできる:

function EntryContainer({ entries }) {
  return (
    <EntryCache entries={entries} isContainer>
      {entries.map(entry => (
        <EntryCard entry={entry} />
      ))}
    </EntryCache>
  )
}

果たして<EntryCache />というHOCみたいなコンポーネントが必要かどうかはさておき、Reactの各コンポーネントを理解可能な要素まで分解するという考え方は好ましい。 これがJSXの1ファイル単位なので、このファイルの役割から見るに以下の点を容易に理解できる。

  1. 必要な入力(props)はentriesの配列である
  2. 期待する出力はEntryCardを内包するEntryCacheである

関数なのだから当然ではあるが、入力に対して出力がある。 ところがERBはテンプレートファイルであるので@entriesが極端な話でいうとこのファイルすべてを上から下まで見渡してみないことには本当にこのファイルが持つ変数が妥当であるのかとか、そういったところがわからない。 だからViewという層はテストできるものの、ERBそのものをテストする術がない。

RubyにもViewComponentというgemがあるのでこのように表現できる:

<div class="container">
  <% cache @entries do %>
    <%= render(EntriesComponent.with_collection(@entries)) %>
  <% end %>
</div>

そもそもRailsではライブラリを使わずにこのような書き方もできる:

<div class="container">
  <% cache @entries do %>
    <%= render @entries %>
  <% end %>
</div>

さらに<%= render @entries, cached: true %>でわずか3行にまで省略できてしまうはずだったのだけれども、私が試した限りでは適切にキャッシュが効いていないようだった。 このようにコードを極限まで省略すれば確かに必要な変数はすぐに理解できるからよいのかというと、やはりコンポーネントではないので気軽にファイルを分割すればよいだけではないのだ。 少なくともRails固有のシンタックスシュガーは正しく理解していないとかえって見づらくなることも多くない。 コーディングゴルフが必ずしも正義とは限らないのだ。

ちょっと複雑なページを同じコントロール配下で使うファイルだからといって分割しつづけていれば延々と増え続けていく。 その結果として管理が煩雑になるのはすぐに想像できる。 そういう意味ではプロジェクトの規模にもよるのだけれども、特定のページでのみ使うViewのテンプレートとページ全体である程度使いまわすことができるAtomic Designのような考え方が理にかなっているように感じた。

コンポーネント化するという概念を実現したViewComponentは現実的な妥協点になりうると思う。 ただしJSXは関数なのでDIVタグをContainerという変数のコンポーネントにも置き換えることができるが、既存のERBではどんなに逆立ちしてもDIVタグである以外の表現はできない。 これはViewComponentを導入しても同じである。

もっと根本的な問題としてはViewComponentの導入を仮にしたとしても、これがRuby on Railsという大きなフレームワークにまだ公式に取り入れられていないことが最大の問題だと思う。 つまりDHHを始めとするRailsのコアチームがこのコンポーネントという考え方を取り入れないのにはなにか私が見落としている重大な問題があるのかもしれないと考えるのが普通である。 すでにWho uses ViewComponent?では複数の導入事例もあるのだけれども、Rubyを書く、ひいてはRailsを書くチームの全体がこのライブラリを認知していなければ使うことはできない。

RSpecのようにテストのデファクトであったり、SidekiqのようにActiveJobのようなラッパーがあるものであればいいのだが、Ruby on Railsのもっと根幹の部分に関わるライブラリであるから今後どれだけ使い勝手がよくなろうともRuby on Rails Guidesを始めとしたドキュメント群に記述が残らない限りは大々的に導入していきましょうとは言いにくい気がする。

Railsで興味深いライブラリにRidgepoleというものがあって、これは既存のrails db:migrateコマンドを改良しようというよりは、まるごと上書きしてしまおうといった試みのように思える。 このライブラリの使い勝手はよいものでもあるけれども私はdb:migrateコマンドの方が好ましいと思っている。 こういったライブラリの導入はこれまでの開発手法が変わるので良い悪いとは別に分断の種にもなりかねない。 仮にどんなに優れたライブラリであってもRailsがひとつのレールであることを考えると、例えばRails 8でAction ViewにComponentのクラスが定義でもされない限り同じRailsであってもいずれどこかでレールが外れるものとなってしまうのである。 個人的にはYAML形式のFixtureは最悪なのでそろそろFactoryBotないしFactoryパターンがRailsのデフォルトになってほしいとは思う。

Ruby on Railsを再定義しようと試みるフレームワークに触れてみたい気持ちもあるが、なかなか思うようにできないのはそういった事情が大きい。 dry-rbとかTrailblazerHanamiなどRubyだけれどRailsではない興味深いフレームワークだって登場しつつある。 でもこういったものを実際に導入しようと思うとそれなりに時間がかかる。 ただチュートリアルに沿って使うだけならまだよいのだが、どうせなら理解して使いたい。 しかし人が何かを学習して習得するまでにはなんらかのコストが発生する。 少なくとも私はそのコストがまあまあ軽視できない量である。 これは自身の賢さや若さと相関するかもしれない。

私がまだRubyの世界に入りたての頃はこのことをあまり理解せずになんでもRailsで作る風潮が疑問だったし、なにかライブラリを作るとこれはRailsで使えますか?というIssueができるのも嫌だった。 ずっとRailsを使い続けてきた今なら理解できるし、私がむしろその質問をする側に回っていても不思議ではない。 理解はできるものの今でもやられて嬉しいものではもちろんないのだけれども。

RailsではStimulusがあって、これも実際に書き始めるとなかなか素のJavaScriptを書いている感覚のような気軽さがある。 Stimulusを書き始めるまではずいぶん長い時間がかかったけれども、未だにRails 5ないし4.2の頃の感覚から抜けきれていないRailsの開発者がまだ少なくはないことを知っている。 そういった人たちにも利益があるようHotwireの啓蒙を内外で広めつつ、従来のViewの書き方にも今後Rails風の改良が加わることを願っている。

Rack層のアプリケーションでいえばパフォーマンス面で比較すればRailsを圧倒するRodaやCubaがあるのだからすべて置き換えられるのかというと当然そんなことはない。 Roda+Sequelの組み合わせも素晴らしいのだが、やはりRails全体が包括しているエコシステムに比べるとRailsを使っている全員がそこに乗り換えられるわけではなさそうである。 早さはWebの世界において重要なファクターであると同時に、必ずしも早さだけが正義とは限らないのである。

だからNode.jsの世界でもなかなかExpressから脱却しきれなかったのは知名度以外の部分もあって、未だにサンプルとしてExpressが採用されているのはそういった事情があるのだろう。 一部の人々によって今後はもうExpressを使うなという過激なタイトルの投稿を見ることもあるけれども、あれだけ習得が容易かつ成熟しきったライブラリを捨てるのはもったいなく思う。

Reactも似たようなフレームワークが登場してはいる。 単純にReactと比較してパフォーマンスが圧倒しているライブラリはJavaScriptのニュースをみていれば様々なものがあったと思う。 それでもやはりReactというフレームワークが今でも生き残っているどころか、第一線なのはやっぱり知名度が大きいのだと思う。 ReactやVueの求人が市場に大量にあるのは言うまでもないが、SvelteやHyperapp、Litなどの求人は私の知る限りではまだ存在しないと思う。 これらのライブラリが今後世間を席巻していくことも期待したい。

またしても最初の話題からだんだん逸れてしまったが、JSXがすぐに使えるという意味ではReactが、ひいてはJSXという仕組みが非常に好ましいものだと思える。 クラスコンポーネントなどの時代を考えると奇跡とすら思える。 とはいえ私が本格的にReactを使うようになったのは実はフックが使えるようになった前後だったから、もう少し早い段階にReactに触れていたらまた違ったかもしれないけれども。 宣言的なコードはSwiftやKotlinにも影響を与えていると思うし、RubyにもViewComponentが生まれた。

今では当たり前に感じてしまっているが、当時Reactはなんでもできるフレームワークだと喧伝されていた時期もあった。 CLIですらReactで書くなんてアイディアもあるようだ。 あまり使う機会は思いつかないのだが、いつか試してみたいと思っている。 それくらいReactとJSXはまだ可能性にあふれている。

Stateなどの考え方は色々変わるのかもしれないが、それでもCreate React Appが第一線から離れたとしても、私はまだまだReactが進化する可能性を信じているし、JSXもその流れに沿って便利な書き方が生まれるかもしれない。 そう考えると単に言語の表現が簡潔であるだけでは収まりきらなかった。

それが私のJSXが好きな理由である。