HTMX――フロントエンド革命、Reactはいらない


[![Group197.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/4d83a9da-91b4-4ab6-9deb-8e400d78c30c.png)](https://leapcell.io/)



**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**

# JavaScriptに完全に囚われたフロントエンド開発

かつて、私はDjangoのTemplateを使って簡単にウェブUIを構築することができました。ページ上のサイズの異なる反復部分については、テンプレートやフラグメントを利用して抽象化またはカプセル化しました。もちろん、必要があれば、ページにインタラクティブ性を追加するためにJavaScriptを書くことも避けませんでした。

しかし、このように構築されたUIには明らかな欠点があります。ユーザーがページとインタラクトするたびに、バックエンドは完全なHTMLページを再送信する必要があります。これはバンドウィドウを浪費するだけでなく、インタラクションを不器用でスムーズでないものにしています。インタラクション機能を強化するために、いくつかのAJAXライブラリが登場しました。時が経つにつれて、JavaScriptはますます多くのタスクを引き受けるようになり、サーバーサイドでのHTMLテンプレートのレンダリング操作さえ徐々にクライアントサイドに移行しました。最終的に、Reactを代表とする反応型のコンポーネントベースのUIが開発の春を迎えました。

Reactはウェブ開発に多くの画期的なコンセプトをもたらしました。たとえば、仮想DOM、片方向データフロー、JSX、コンポーネントベースの考え方などです。それはフロントエンドをHTML中心のクライアントから完全にJavaScript中心のクライアントに変えました。同時に、バックエンドもフロントエンドのレンダリングの舞台から後退し、HTMLを生成する主導権をフロントエンドに与えると同時に、データAPIの提供に焦点を当てました。しかし、JavaScriptがフロントエンド開発を完全に引き受け始め、HTMLが二次的な役割になったとき、事態は少しコントロールできなくなり始めました。完全に静的なHTMLで構成されたフッターコンテンツでさえ、JavaScript（JSX）を通じて実装しなければなりません。元々コンテンツが豊富なHTMLページは極めてシンプルになり、bodyにはマウント用のルート要素しか残っていません。

Reactがフロントエンドの世界で人気を博した一方で、その欠点も徐々に明らかになりました。これらの問題を解決するために、新しいアイデア、フレームワーク、エコシステムツールが次々と登場しています。情熱的なフロントエンド開発者たちは、山を切り開いて川に橋を架けるように、新たに生じる問題に対する解決策を探し続けています。適切な状態管理ツールがなかったとき、Reduxが生み出されました。状態管理があまりにも複雑だと考えられたとき、Hooksが導入されました。JavaScriptクライアントがSEOにやさしくないとき、SSRのような技術が採用されました。様々な解決策が積み重なるにつれて、元々シンプルな解決策はますます複雑になり、最終的には巨大で入り乱れた「フランケンシュタイン」が形成されました。現在、私はフロントエンド開発に対して恐怖の念を抱いています。元々シンプルなタスクが今では様々な面倒な詳細に溢れています。フロントエンドコードを書くとき、私はしばしば過剰な複雑さに圧倒されます。

さらに悪いことに、JavaScriptがフロントエンド開発を完全に引き受けたため、フロントエンドプロジェクトの規模が拡大し続け、それがTypeScriptをベストプラクティスにしています。私はTypeScriptを軽んじているわけではありません。実際、TypeScriptは設計の良い言語であり、JavaScriptが大規模なフロントエンドプロジェクトで抱える多くの問題をうまく解決しています。しかし、本当にどこにでも「大規模」なフロントエンドプロジェクトが必要なのでしょうか？ フロントエンドプロジェクトをこれほど面倒で肥大化させる根本的な原因は何でしょうか？ 当初、これらのフレームワークの主な目的は、フロントエンドをより反応的に、再利用しやすく、表現力豊かにすることではありませんでしたか？ しかし、現在、Reactや次々と登場した多くのフロントエンドフレームワーク（Vue、SolidJS、Svelteなど）は、その複雑さにより、フロントエンド開発者、特に私のような「偽のフロントエンド開発者」にとって、小規模のプロジェクトを大規模なものに、シンプルなプロジェクトを複雑なものに変えさせています。そのため、複雑なプロジェクトに対処するために、TypeScriptを選択することは避けられないように思えます。

## HTMX：HTMLの本来の意図に戻る

HTMXという名前がどこから来たのかは確信がありませんが、そのビジョンから推測すると、HTML eXtensionを意味しているのではないかと思います。HTMXは、私たちがHTMLを強化して発展させることに焦点を当てるべきだと考えています。HTMLの多くの欠点は、より良い意味論化を通じて補うことができます。たとえば、タグ属性を合理的に利用することで、直接JavaScriptがHTMLを置き換えることなく、その欠点を補うことができます。これはhtmx.orgでの直接的な紹介です：「htmxは、属性を使用して、AJAX、CSSトランジション、WebSocket、サーバー送信イベントに直接HTMLでアクセスできるようにします。そのため、ハイパーテキストのシンプルさとパワーを使って、現代的なユーザーインターフェイスを構築できます」

HTMXの核心的なビジョンと特徴は以下の通りです：
1. **漸進的な強化**：HTMXは漸進的な強化の原則に基づいて設計されており、これはJavaScriptがなくてもページが適切に動作することを最初に保証し、その後徐々に機能を追加することを意味します。
2. **低侵襲性**：HTMXは既存のコードに対してできるだけ低侵襲性を保つように努めています。アプリケーション全体を書き直す必要はありません。必要な場所にいくつかの属性を追加するだけで、HTMXを使い始めることができます。
3. **純粋なHTML**：HTMXを使えば、JavaScriptを書かずに多くの複雑なフロントエンド機能を実装することができます。これにより、コードがより理解しやすく、保守しやすくなります。特に、HTMLとサーバーサイドのプログラミングにより精通している開発者に適しています。
4. **既存の技術との互換性**：HTMXは既存のフレームワークやライブラリとシームレスに連携することができます。Flask、Django、Rails、またはその他のバックエンドフレームワークを使っている場合でも、HTMXを簡単に組み込むことができます。
5. **小さくて高速**：多くの現代的なフロントエンドフレームワークに比べて、HTMXは非常に軽量です。これは、それがより高速にロードされ、ユーザーエクスペリエンスを向上させることができることを意味します。

これらの特徴から、HTMXの目的はフロントエンド開発を単純化し、開発者が大量のJavaScriptや複雑なフロントエンドフレームワークを使わずに、迅速かつ効率的に高いインタラクティブ性と反応性を備えたウェブページを作成できるようにすることであることがわかります。

複雑なフロントエンドフレームワークや大量のJavaScript開発が必要ないとき、現在のフロントエンドが直面している多くの問題が簡単に解決されることに気づくでしょう。たとえば、ページ全体を完全にJavaScript化する必要がなくなり、ページのJavaScript化によって引き起こされるSEOの問題を心配する必要がなくなり、複雑な状態を管理する必要がなくなり、エンジニアリングの問題を解決するためにTypeScriptを導入する必要がなくなります。

## サンプルコードの比較

まず、HTMXの下で典型的なフロントエンド機能であるオートコンプリートをどのように実装するか見てみましょう。

```html
<input type="text"
       hx-trigger="keyup delay:500ms, custom-event"
       hx-get="/search"
       hx-target="#search-results"
       hx-swap="innerHTML">
<div id="search-results"></div>
```

あまり驚かないでください。この小さなコードがHTMXバージョンのオートコンプリートのすべてのコードです。私たちは、HTMXが通常のHTMLタグにいくつかの重要な属性を追加していることがわかります：
1. **hx-trigger**：hx-triggerは、hx-action（たとえばAJAXコール）をいつ、どのようにトリガーするかを指定するために使用されます。この属性を通じて、開発者は特定のイベント（たとえば、クリック、入力、またはフォーカスなど）が発生したときにサーバーとのインタラクションをどのように開始するかを制御できます。この例では、2つのトリガーイベントが設定されています：a）入力欄でkeyupイベントが発生し、500msの遅延の後にトリガーする；b）custom-eventという名前のイベントを受け取ったときにトリガーする。
2. **hx-get**：htmxアクションがトリガーされたときに実行するコール。hx-getはGETリクエストを表します。同様に、hx-post、hx-put、hx-delete、hx-patchなどもサーバーコールに使用することができます。hx-getのような属性を通じて、HTMXはサーバーとのインタラクションの権限を各タグに分散させ、従来の<a/>と<form/>だけがサーバーとインタラクションできるという制限を変えます。
3. **hx-target**：サーバーのレスポンスが返されたとき、レスポンスが埋め込まれる場所を指定します。hx-targetは任意のCSS式にすることができます。ここでは、idがsearch-resultsのノードを指しています。デフォルトでは、現在のノードです。hx-getのような属性がページ内でサーバーとのインタラクションの能力を提供するのであれば、hx-targetはページ内で動的に更新する能力を提供します。
4. **hx-swap**：サーバーのレスポンスが返されたとき、コンテンツをどのように交換または置き換えるかを指定します。デフォルトではinnerHTMLで、つまり#search-resultsの中のHTMLがサーバーから返されたデータで置き換えられます。hx-swapには、outerHTML、beforeend、afterendなどの他の動作もあり、どのように交換するかにアニメーション効果を追加することさえできます。詳細はドキュメントを参照してください。

これらの属性は初心者にとって非常に役立ちます。これらを習得することで、ページ内のほとんどのインタラクションを扱うことができます。HTMXは他にも多くのhx-*属性を提供していますが、ここでは一つ一つ紹介しません。これらの属性を使えば、検索ボックスの動作を簡単に制御し、元々は大量のJavaScriptが必要だった効果を実現することができます。

もう少し複雑な例を見てみましょう。

アプリケーションがいくつかのノートブックを表示し、各ノートブックにはいくつかのノートがあり、各ノートには詳細情報があるとします。私たちはこれを三つの列にレイアウトして表示します。ユーザーが一番左の列のbook1をクリックすると、book1の下にあるノートがページネーション形式で二番目の列に表示され、その後、二番目の列の最初のノートの詳細が三番目の列に表示されます。

ここにHTMXを使って実装したコードがあります：
1. **左の列のコード**：
```html
<ul>
{% for book in books %}
<li>
  <a hx-get="/books/{{book.id}}" hx-target="#note-list">{{book.name}}</a>
</li>
{% endfor %}
</ul>
```
2. **真ん中の列のコード**：
```html
<div id="note-list">
{% for note in current_book.notes %}
<div onclick="htmx.trigger('#note-detail', 'loadNote', {id: '{{note.id}}'})">
  <h2>{{note.title}}</h2>
  <p>{{note.summary}}</p>
</div>
{% endfor %}
</div>
```
3. **右の列のコード**：
```html
<div id="note-detail"
      _="on loadNote(data) from body
         htmx.ajax('GET', `/notes/${data.id}`, '#note-detail')"
>
  <h3>{{title}}</h3>
  <p>{{detail}}</p>
</div>
```

このように、いくつかのシンプルなテンプレートとHTMX属性を補って、最初のページのレンダリングだけでなく、ページをユーザーのクリックに応じて更新することができます。たとえば、ユーザーがbook2をクリックすると、GETリクエストがトリガーされて/books/2にアクセスし、次のようなレスポンスが返されます（つまり、真ん中の列のテンプレートで生成されたコンテンツ）：

```
200 OK
HX-Trigger: {"loadNote": {"id": "book2id1"}}
Content-Type: text/html

<div onclick="htmx.trigger('#note-detail', 'loadNote', {id: 'book2id1'})">
  <h2>Hello 1</h2>
  <p>World 1</p>
</div>
<div onclick="htmx.trigger('#note-detail', 'loadNote', {id: 'book2id2'})">
  <h2>Hello 2</h2>
  <p>World 2</p>
</div>
...
```

この結果はHTMXによって#note-listにレンダリングされ、これにより真ん中の列の更新が実現されます。同時に、返されたデータのHX-TriggerヘッダーにloadNoteイベントが含まれているため、このイベントは#node-detailによって捕捉され、/notes/book2id1にGETリクエストを送信し、その後のレスポンスが右の列にレンダリングされます。

上記の機能について、Reactを使って実装する場合は、コード量が大幅に増加し、より多くの状態管理とコンポーネント間のインタラクションロジックを処理する必要があります。ここに簡単なReactの実装例（簡略化されたバージョンで、核心的なロジックのみを示しています）があります：

```jsx
import React, { useState, useEffect } from'react';

const BookList = ({ books }) => {
  const [currentBook, setCurrentBook] = useState(null);
  const [currentNote, setCurrentNote] = useState(null);

  useEffect(() => {
    if (books.length > 0) {
      setCurrentBook(books[0]);
      if (books[0].notes.length > 0) {
        setCurrentNote(books[0].notes[0]);
      }
    }
  }, [books]);

  const handleBookClick = (book) => {
    setCurrentBook(book);
    if (book.notes.length > 0) {
      setCurrentNote(book.notes[0]);
    }
  };

  const handleNoteClick = (note) => {
    setCurrentNote(note);
  };

  return (
    <div className="flex">
      <div className="w-1/3">
        <ul>
          {books.map((book) => (
            <li key={book.id} onClick={() => handleBookClick(book)}>
              {book.name}
            </li>
          ))}
        </ul>
      </div>
      <div className="w-1/3">
        {currentBook && (
          <div>
            {currentBook.notes.map((note) => (
              <div key={note.id} onClick={() => handleNoteClick(note)}>
                <h2>{note.title}</h2>
                <p>{note.summary}</p>
              </div>
            ))}
          </div>
        )}
      </div>
      <div className="w-1/3">
        {currentNote && (
          <div>
            <h3>{currentNote.title}</h3>
            <p>{currentNote.detail}</p>
          </div>
        )}
      </div>
    </div>
  );
};

export default BookList;
```

同じ機能をReactで実装するには、ページの状態を管理するために複数の状態変数（currentBookやcurrentNoteなど）を定義する必要があり、コンポーネント内でユーザーのインタラクションを処理するためにより多くのイベントハンドリング関数（handleBookClickやhandleNoteClickなど）を書く必要があることがわかります。対照的に、HTMXの実装方法はより簡潔で、ロジックがより明確で、伝統的なサーバーレンダリングの考え方に近いものになっています。

## 結論

HTMXは、非フロントエンドエンジニアにとってフロントエンド開発の扉を再び開きました。スプレッドシートやGoogleマップのように、極めて高いインタラクティブ性を持つアプリケーションを開発していない場合、基本的にはHTMXをうまく利用して、既存のフロントエンド開発フレームワークを置き換え、HTMLを中心とした軽量なフロントエンド開発モードに戻ることができます。HTMXを使えば、クライアントをSPA（シングルページアプリケーション）にするかMPA（マルチページアプリケーション）にするかという問題に苦労する必要がありません。ルーティングに最適な方法を選ぶことができ、最も自然な方法でデータを表示し、ユーザーがデータとインタラクトできるようにすることができます（作成、読み取り、更新、削除、またはその他の操作であっても）。

現在、HTMXのエコシステムはまだ幼年期にあります。私は本当に主流のバックエンドフレームワークがそれに対して深いサポートを提供し、さらには統合することを楽しみにしています。HTMXの価値が絶えず発見されるにつれて、非フロントエンド開発者が自信を取り戻し、ウェブフロントエンドを含む完全な製品を簡単に開発できるようになると信じています。

# [Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)

最後に、ウェブサービスをデプロイするのに最適なプラットフォームをお勧めします：**[Leapcell](https://leapcell.io/)**

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/6f4e3979-576a-4b41-8bd4-23b4de7b40f3.png)](https://leapcell.io/)


### 🚀 好きな言語で構築
JavaScript、Python、Go、またはRustで簡単に開発できます。

### 🌍 無料で無制限のプロジェクトをデプロイ
使用した分だけ支払います—リクエストがなければ、請求はありません。

### ⚡ 使った分だけ支払い、隠された費用はありません
アイドル料はなく、シームレスなスケーラビリティが備わっています。

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/3806309a-c100-4959-81be-744c9a91db68.png)](https://leapcell.io/)

[📖 ドキュメントを探索](https://docs.leapcell.io/)  

🔹 Twitterでフォロー：[@LeapcellHQ](https://x.com/LeapcellHQ)