22
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rails7】図解でTurbo Frames入門

Last updated at Posted at 2023-05-14

はじめに

後掲のテキストは、SPA風のCRUDアプリ作りを通して、無料でTurboを学ぶことができているめちゃくちゃありがたいテキストです。
ただし、フロントエンドの知識がないと少々難しく、何が起きている?って状態でした。
デベロッパツールで通信の様子を確認すると理解が進んだため、図も併用しながらその様子を解説していきます。

対象読者

  • Turboについて興味がある方
  • Railsチュートリアル完走後、フロントエンド開発に興味がある方

動作環境

  • Ruby - 3.1.0
  • Rails - 7.0.2.3
  • Google Chrome - 113.0.5672.63
  • 上記テキストのチュートリアル1が終わったタイミングから解説します。
(お急ぎでcloneしたい方はこちら)
```
$ git clone <https://github.com/shita1112/cat-hotwire-demo>
$ cd cat-hotwire-demo
$ git checkout -b tutorial-1  origin/tutorial-1
$ yarn install
$ bundle install
$ rails db:create
$ rails db:migrate
$ rails db:seed
```

Turboとは

Web開発においては以下の3つの技術で構成されておりDrive⇒Frames⇒Streamsの順に複雑になります。

  • Turbo Drive
  • Turbo Frames
  • Turbo Streams

ここで、Turbo Driveに関するテキストの説明を引用します。

🐱 Turbo Driveは画面遷移を高速にしてくれるTurboの機能だよ
🐱 Turbo DriveはTurbolinksの名前を変えたもので、基本的な機能はTurbolinksと同じだよ。リンク、フォームのリクエストをTurbo Driveがインターセプトして、fetchによる非同期リクエストに差し替える。そしてレスポンスされたHTMLの

要素だけを抜き出して、現在のページの要素を置換する。
🐱 通常の画面遷移がHTMLを丸ごと変えるに対して、Turbo Driveでの画面遷移はだけを置換するよ(正確にはの置換に加えて、の一部がマージされる)。
🐱 これの何が嬉しいかと言うと、画面遷移しても今のページのCSS・JavaScriptをそのまま利用できるため、CSS・JavaScriptを初期化してページに適用する処理をスキップできるんだ。これによって画面遷移が高速になるよ。

すごくわかりやすいのですが、知らない単語があると難しいですよね。
Turboなし⇨Turbo Drive適用⇨Turbo Frames適用と、順を追って挙動を確認することで理解していきたいと思います。

通常の画面遷移(Turboなし)について

そもそも通常の画面遷移はどのようなことが起こっているのかデベロッパツールを使用して確認してみましょう。

  1. 最初にTurboが無効になっていることを確認してください。

    app/javascript/application.js
    Turbo.session.drive = false // この記述があることを確認する
    
  2. $ bin/devでサーバーを立ち上げ、/catsにアクセスします。

  3. GoogleChromeをお使いの方はF12を押してデベロッパツールを開き、Networkタブを選択した状態でページネーションを切り替えてみます。
    nomal.gif

ページネーションを切り替えてみるとツール上で次のような複数のファイルが読み込まれたことがわかると思います。

Name ファイルの種類
cats?page=4 HTML
application~.js JavaScript
assets/fonts/bootstrap-icons~ CSS
assets/fonts/bootstrap-icons~ CSS
favicon.ico 画像

このように通常の画面遷移ではリクエストに応じてコンテンツに必要となる全てのファイルが読み込まれます(正確にいうとリクエストが5回発生している)

また、読み込まれたファイル選択し、タブを切り替えて詳細を確認してみましょう
header.gif

リクエストヘッダー

RequestURLはhttp://127.0.0.1:3000/cats?page=4となっています。これはページネーションの4をクリックしたからですね。そして、このリクエストにはオプションを追加するヘッダーが存在します。リクエストヘッダー内のAcceptはtext/html,application/xhtml+xml,application/〜略となっています。このようにオプションを付与し、受け取ることができるフォーマットを明示して、サーバー側にリクエストを出しています。

レスポンスヘッダー

レスポンスヘッダーを見てみるとContent-Type: text/html; charset=utf-8となっています。リクエストのAcceptに対応したフォーマットをサーバーが返していることがわかります。

レスポンスボディ

最後にレスポンスの中身を確認してみます。
body.gif
プレビューではCSSが反映されていない、HTMLのレンダリング結果を確認することができます。そして、レスポンスボディ内でサーバーから返された名前のHTMLを確認できます。中身はレイアウトファイルであるapplication.html.erbの内容が含まれた完全なHTMLです。

まとめると、通常の画面遷移ではページネーションでページを切り替えるなどのリクエストに対して、サーバーはページを構成するHTMLやCSSなどの全ての要素、かつHTMLも完全なものを返しています。

Turbo Driveで画面遷移を高速化

本題のTurbo Driveによる高速化を体感してみましょう。これまでは通常の画面遷移の様子を確認するために意図的にTurbo Driveを無効にしていましたが、有効に切り替えます

app/javascript/application.js
Turbo.session.drive = false // この記述を削除する

F12でデベロッパツールを一度閉じ、再度開いた状態でページネーションを切り替えてみます。
fetch.gif
いかがでしょうか。まず読み込まれたファイルの数が一気に2つに減りました。また、コンテンツのタイプがfetchとなっております。Turbo Driveが有効になると、リンクをクリックしたりフォームを送信する操作をTurbo Driveが検知して非同期(fetch)で通信を行うようになります。
非同期通信というのは、簡単にいうと現在の画面の状態は変更せずに、バックグラウンドで通信を行う技術です。Turbo Driveと組み合わせることで通信結果(レスポンス)が返ると画面に反映されます。
通常の画面遷移では通信中は画面を操作することができません。(現在はPCやブラウザの性能向上でそれほど体感できませんが。。)

Turbo Driveによるリクエストとレスポンス

通信結果のヘッダーとボディも確認してみましょう。
drive-hb.gif
ここで変化があるのはリクエストヘッダー内のAcceptがtext/html, application/xhtml+xmlのみになっているところです。その他は特に変わりなく、レスポンスボディとしても完全なHTMLを返していることがわかります。

Turbo Driveまとめ

ここまでくれば本題のTurbo Driveの内容が理解できると思います。

🐱 Turbo DriveはTurbolinksの名前を変えたもので、基本的な機能はTurbolinksと同じだよ。リンク、フォームのリクエストをTurbo Driveがインターセプトして、fetchによる非同期リクエストに差し替える。そしてレスポンスされたHTMLの<body>要素だけを抜き出して、現在のページの<body>要素を置換する。

置換の様子を図に表すと次の通りです。
body置換.png
ページ切り替えの非同期リクエストに対して、レスポンスされたHTMLからTurboDriveが<body>要素を抽出してまるっと置き換えて、ページ切り替えを実現しているのです。

Turbo Frames

Turbo Driveは<body>タグ全てを置換しますが、Turbo Framesは指定した領域のみを置換します。
ここではテキストで紹介のページネーションのTurbo Frames化について解説します。

TurboFrames化するには以下のように置換したい領域を<%= turbo_frame_tag "cats-list" do %><% end %>で囲い、turbo_frame_tagヘルパーに任意のidを付与するよう引数を渡して指定します。

# app/views/cats/index.html.erb
<div class="card-body mx-3">
    <%= turbo_frame_tag "cats-list" do %>
      <div class="row py-2">
        <div class="col-4 mt-auto">
          <%= sort_link(@search, :name) %>
        </div>
        <div class="col-4 mt-auto">
          <%= sort_link(@search, :age) %>
        </div>
        <div class="col-4 d-flex justify-content-end">
          <%= link_to icon_with_text("plus-circle", "登録"),
                      new_cat_path,
                      class: "btn btn-outline-primary"
          %>
        </div>
      </div>

      <%= render @cats %>

      <div class="d-flex justify-content-end mt-3">
        <%= paginate @cats %>
      </div>
    <% end %>
  </div>
</div>

Turbo Framesによるリクエストとレスポンス

先ほどと同様に通信の様子を確認してみましょう
frames.gif
Trubo Drivesとの違いは3つあります。

  • リクエストヘッダーにTurbo-Frame: cats-listが付与されることで、Turbo Framesを使用することを明示し、かつ置換する領域のcats-listを指定しています。ページネーションは<%= turbo_frame_tag "cats-list" do %><% end %>で囲われた領域に存在するため、リンクをクリックするとTurbo Framesが非同期通信を開始します。

  • レスポンスボディとしてapp/views/cats/index.html.erbのコンテンツのみが返されています。これまでは<head><body>も含まれた完全なHTMLが返されていましたが、Turbo Framesリクエストにより、サーバーはレイアウトコンテンツを除いたHTMLを返します。

  • turbo_frame_tagヘルパーにより以下のHTMLが生成されています。

    <turbo-frame id="cats-list"></turbo-frame>
    

Turbo Framesによる置換の様子を図示します。
frame.png

ページを切り替えの非同期リクエストに対して、レスポンスされたHTMLからTurboFramesが<turbo-fame>要素を抽出し、対応するidの要素に置き換えてページ切り替えを実現しています。よって、同リクエストで置き換えられることがない検索フォームなどは状態を保持したままにできるのです。

編集のTurbo Frames化

Turbo Framesのもう一つの事例として編集のインラン表示化の様子を確認してみましょう。

編集のTurbo Frames化を試す(お急ぎの方はこちら)
$ git checkout -b tutorial-2  origin/tutorial-2
$ git branch test-frames 753d984
$ git checkout test-frames
$ bin/dev

まず編集クリック前のHTMLを確認してみます。
html.gif
先ほど記述したcats-list下にid="cat_320"などの独立したidが付与されたturbo-framecatオブジェクト分生成されていることがわかります。

これは以下のようにturbo_frame_tagにcatオブジェクトを渡すと、turbo_frame_tagヘルパー内で利用されているdom_idヘルパーが、cat.idから生成してくれているからなのです。

# app/views/cats/_cat.html.erb
<%= turbo_frame_tag cat do %> # 内部でdom_idヘルパーを使用
  <div class="row py-2 border-top">
    <div class="col-4 my-auto">
      <%= cat.name %>
    </div>
    <div class="col-4 my-auto">
      <%= cat.age %>
    </div>
    <div class="col-4">
      <div class="d-flex justify-content-end">
        <%= link_to "編集", edit_cat_path(cat), class: "btn btn-sm btn-outline-primary me-2" %>
        <%= button_to "削除", cat, method: :delete, class: "btn btn-sm btn-outline-danger" %>
      </div>
    </div>
  </div>
<% end %>

編集クリック時に表示されるフォームも以下のように修正し、turbo_frame_tagにcatオブジェクトを渡しています。

# app/views/cats/_form.html.erb
<%= turbo_frame_tag cat do %>
  <%= bootstrap_form_with(model: cat) do |form| %><% end %>
<% end %>

Turbo Framesによるリクエストとレスポンス - その2

これを踏まえた上で編集ボタンをクリックした時の通信の様子を確認してみます。
frames-edit.gif
確認できるポイントは次の通りです

  • リクエストヘッダのTurbo-Frame: cat_320 これによりTurbo Framesを使用したリクエストを送信している
  • レスポンスデータはレイアウトコンテンツを除いたedit.html.erb配下のコンテンツのみとなっている
  • レスポンスデータのフォームは<turbo-frame id=”cat_320”>で囲われており、置換先の要素を指定している

Turbo Framesによる置換をイメージでも確認してみましょう
frame-edit.png

編集ページの非同期リクエストに対して、レスポンスされたHTMLからTurbo Framesが要素を抽出し、対応するidの要素に置き換えることで、より小さな要素単位で画面変更ができていることがわかりますね。

Turbo Framesまとめ

  • turbo_frame_tagヘルパーにより<turbo-frame>が生成される
  • turbo_frame_tagヘルパーへ、@catなどのオブジェクトを渡すことによって固有のidが付与される
  • <turbo-frame>内のリンクがクリックされるとTurbo Framesリクエストが出され、サーバーはレイアウトコンテンツを除いたHTMLをレスポンスする
  • レスポンス結果からTurbo Framesがidに対応する<turbo-frame>を置換する

テキスト内で続くバリデーション失敗時、更新成功時、キャンセル時も同様にして<turbo-frame>のid単位で置換を実施します。

最後に

バックエンドでデバッグツールを用いてメソッドなどの挙動を確認すると理解が深まるように、フロントエンド開発でもデベロッパツールを用いて通信の様子を確認することで理解が深まることが体感いただけたのではないでしょうか。Turbo Streamsに関しても同じように挙動を確認することができますので、ぜひ試してみてください!

22
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?