🍣

HTMLだけでモダンなUI!Express×Turboでシンプル検索ページを作ろう

2024/11/15に公開

こんにちは!👋

突然ですが、みなさんこんな経験ありませんか?

  • 「React難しすぎ...」
  • 「Vue.jsの設定めんどくさい...」
  • 「フロントエンドのコード量多すぎない?」
  • 「もっとシンプルに作れないの?」
  • 「JS難しすぎる...」
  • 「ちょっと動的にしたいだけなのにめんどくさくない?」

今回は、そんな悩みを解決できるかもしれない「Hotwire」というツールを使って、JSをほとんど書かずにモダンなUIを作る方法を紹介します!

なんでこの記事を書いたの?

実は面白い話があって...hotwireのアドベントカレンダーを見てたら、なんと一日目が空いてるじゃないですか!「おっ、これは入れてもいいんじゃね?」と思って、勢いで予約しちゃいました(笑)

普段、Hotwireって「Railsの人たちが使うやつでしょ?」みたいなイメージありますよね。でも実は、他のバックエンドでも使えるんです!今回はExpressでHotwireを使ってみます。

...正直に言うと、僕がExpressしか上手く書けないからです

この記事を読むべき人

  • 「Hotwireって何?」って思ってる人
  • 「SPAとかMPAとか分からない...」という人
  • 「フロントエンドの実装疲れた...」という人
  • 「JSゴリゴリ書くのしんどい」という人
  • 「とにかくシンプルに作りたい!」という人

Hotwireって何者?

まずは、Hotwireについて説明していきましょう!

説明(公式から)

Hotwireは、JSONの代わりにHTMLを送信することで、あまりJavaScriptを使用せずにモダンなウェブアプリケーションを構築するための代替アプローチです。

「???」ってなりますよね(笑)

意訳すると...

「JSほとんど書かなくても、イケてるWebアプリが作れちゃう魔法のような仕組み」 です!

今までのモダンなWebアプリ開発って、こんな感じでしたよね:

  1. サーバーからJSONをもらう
  2. フロントエンドでゴリゴリJSを書く
  3. HTMLに変換する
  4. 画面に表示する

でもHotwireなら:

  1. サーバーから完成したHTMLが届く
  2. はい、終わり!

めっちゃ簡単じゃないですか?

Hotwireの正体

実は「Hotwire」って、2つのJSフレームワークのセットなんです:

  • Turbo:今回の主役!HTMLを賢く扱うやつ
  • Stimulus:必要な時だけJSを追加できるやつ

今回は特に便利な「Turbo」について掘り下げていきましょう!

早速実装していこう!

今回は「ユーザー検索ページ」を作っていきます。段階的に改良して、Turboの便利さを体感していきましょう!

Step 1:普通のMPAで作ってみる

まずは、いつものようにシンプルなMPAとして作ってみましょう。

バックエンドのコードはこんな感じ:

const userSchema = mongoose.Schema({
  name: String,
})

const User = mongoose.model("User", userSchema) 
// 👆 userモデルにはダミーデータを入れてます

app.get("/", (req, res) => {
  res.render("index")
})

app.get("/users", async (req, res) => {
  console.log(req.query.name)
  const users = await User.find({ name: new RegExp(req.query.name, "i") }) 
  // 👆 正規表現で名前を探してます
  res.render("users", { users })
})

index.ejsはこんな感じ:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/linkalls/raku@beta-v0.01/raku.min.css">
</head>
<body>
  <form method="get" action="/users">
    <label for="name">名前を入力</label>
    <input type="text" name="name" id="name">
    <button>submit</button>
  </form>
</body>
</html>

users.ejsもこんな感じ:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form method="get" action="/users">
    <label for="name">名前を入力</label>
    <input type="text" name="name" id="name">
    <button>submit</button>
  </form>
  <ul>
    <% if (users.length === 0) { %>
      <li>ユーザーが見つかりませんでした</li>
    <% } else { %>
      <% users.forEach(user => { %>
        <li><%= user.name %></li>
      <% }) %>
    <% } %>
  </ul>
</body>
</html>

動かしてみると...

レコーディング 2024-11-01 220501.gif

うーん...いまいちですね

  • CSSが検索結果ページで消えちゃう
  • 同じフォームを2回書かないといけない
  • 2024年なのにこれはちょっと...

Step 2:Turbo Drive投入!

ここでTurbo Driveの出番です!

Turbo Driveって何よ?

簡単に言うと「ページ遷移の賢い子」です!

特徴:

  • リンクやフォームの送信を見張ってくれる
  • 必要な部分だけを更新してくれる
  • ブラウザの戻る・進むボタンもちゃんと動く

導入方法

驚くべきことに、たった1行追加するだけです!

<!-- index.ejsのhead内に追加 -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.1.0/dist/turbo.es2017-esm.min.js"></script>

「え、これだけ...?」って感じですよね。

実行してみると...

レコーディング 2024-11-01 221803.gif

おお!?なんかヌルヌル動くようになりました!✨

  • ページ遷移がスムーズに
  • CSSの再読み込みもなし

でも...まだ問題が残ってます。フォームが2回書かれてるんですよね

Step 3:最終兵器、Turbo Frame登場!

ここで最後の切り札、Turbo Frameの登場です!

Turbo Frameって何よ?

簡単に言うと「部分更新の達人」です。ページの特定の部分だけを更新できちゃうスグレモノです!

これを使えば:

  • フォームを2回書く必要なし!
  • 更新も必要な部分だけ!
  • コードもスッキリ!

実装方法

まずは、index.ejsを更新します:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/linkalls/raku@beta-v0.01/raku.min.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@7.1.0/dist/turbo.es2017-esm.min.js"></script>
</head>
<body>
  <!-- 👇 ここにdata-turbo-frame="users"を追加! -->
  <form method="get" action="/users" data-turbo-frame="users">
    <label for="name">名前を入力</label>
    <input type="text" name="name" id="name">
    <button>submit</button>
  </form>
  <!-- 👇 ここから追加!ここが更新される場所になります -->
  <turbo-frame id="users">
    ここに検索結果が出るよ!
  </turbo-frame>
</body>
</html>

「あれ?data-turbo-frame="users"ってなに?」って思いましたよね?
これは「このフォームの結果を id="users" のturbo-frameに表示してね♪」っていう指示なんです。簡単でしょ?

次に、users.ejsも更新します:

<!-- 👇 必要な部分だけにスリム化! -->
<turbo-frame id="users">
  <ul>
    <% if (users.length === 0) { %>
      <li>ごめんなさい...ユーザーが見つかりませんでした🥺</li>
    <% } else { %>
      <% users.forEach(user => { %>
        <li><%= user.name %></li>
      <% }) %>
    <% } %>
  </ul>
</turbo-frame>

ポイントは2つだけ:

  1. 同じ id="users" を指定すること
  2. 表示したい内容を turbo-frame タグで囲むこと

動かしてみよう!

画面録画 2024-11-01 223546.gif

うおおお!?これはすごい!

  • 入力に合わせてサクサク更新
  • ページ全体のリロードなし
  • フォームの重複もなし

さらにすごいのは、更新されるのが turbo-frame で囲った部分だけということ!

画面録画 2024-11-01 223817.gif

見てください!この部分更新の美しさ!

まとめ:ここがすごいよ、Turbo!

実装してみて分かった「Turboのイイところ」を整理してみましょう:

  1. JSほぼ書かなくていい!

    • 「フロントエンド疲れ」から解放されます
    • コード量が激減します
  2. 導入が超カンタン!

    • CDN一行追加するだけ
    • 既存のプロジェクトにも導入しやすい
  3. パフォーマンスもバッチリ!

    • 必要な部分だけ更新
    • ページ遷移がスムーズ
  4. 学習コストが低い!

    • HTMLの知識があれば OK
    • 特別な書き方を覚える必要なし

次のステップは?

基本的な使い方は分かったと思いますが、ここからできることはまだまだあります:

  • 複数のTurbo Frame併用
  • Stimulusと組み合わせて動的な処理追加
  • ページネーションへの適用
  • など...
  • この記事ではそこまで深掘りしませんが知りたくなった方は下の本を読んでみるといいと思います

https://zenn.dev/shita1112/books/cat-hotwire-turbo

さいごに

「フロントエンド疲れ」してませんか?もっとシンプルに作りたいと思ってませんか?

そんなあなたに、ぜひTurboを試してほしいです!きっと新しい発見があるはずです。

完全なソースコードはこちらで公開してます:
https://github.com/linkalls/hotwire-js/tree/28571158f8063856d14c1815e1f8fece1f98b126

「こんな使い方もできるよ!」とか「このコードなおしたほうがいいかも」とか、どんな意見でもコメント待ってます!

それでは、Happy Hotwiring! 🚀✨

参考リンク

Discussion