はじめに
後掲のテキストは、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なし)について
そもそも通常の画面遷移はどのようなことが起こっているのかデベロッパツールを使用して確認してみましょう。
-
最初にTurboが無効になっていることを確認してください。
app/javascript/application.jsTurbo.session.drive = false // この記述があることを確認する
-
$ bin/dev
でサーバーを立ち上げ、/cats
にアクセスします。 -
GoogleChromeをお使いの方はF12を押してデベロッパツールを開き、Networkタブを選択した状態でページネーションを切り替えてみます。
ページネーションを切り替えてみるとツール上で次のような複数のファイルが読み込まれたことがわかると思います。
Name | ファイルの種類 |
---|---|
cats?page=4 | HTML |
application~.js | JavaScript |
assets/fonts/bootstrap-icons~ | CSS |
assets/fonts/bootstrap-icons~ | CSS |
favicon.ico | 画像 |
このように通常の画面遷移ではリクエストに応じてコンテンツに必要となる全てのファイルが読み込まれます(正確にいうとリクエストが5回発生している)
また、読み込まれたファイル選択し、タブを切り替えて詳細を確認してみましょう
リクエストヘッダー
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に対応したフォーマットをサーバーが返していることがわかります。
レスポンスボディ
最後にレスポンスの中身を確認してみます。
プレビューではCSSが反映されていない、HTMLのレンダリング結果を確認することができます。そして、レスポンスボディ内でサーバーから返された名前のHTMLを確認できます。中身はレイアウトファイルであるapplication.html.erb
の内容が含まれた完全なHTMLです。
まとめると、通常の画面遷移ではページネーションでページを切り替えるなどのリクエストに対して、サーバーはページを構成するHTMLやCSSなどの全ての要素、かつHTMLも完全なものを返しています。
Turbo Driveで画面遷移を高速化
本題のTurbo Driveによる高速化を体感してみましょう。これまでは通常の画面遷移の様子を確認するために意図的にTurbo Driveを無効にしていましたが、有効に切り替えます
Turbo.session.drive = false // この記述を削除する
F12でデベロッパツールを一度閉じ、再度開いた状態でページネーションを切り替えてみます。
いかがでしょうか。まず読み込まれたファイルの数が一気に2つに減りました。また、コンテンツのタイプがfetchとなっております。Turbo Driveが有効になると、リンクをクリックしたりフォームを送信する操作をTurbo Driveが検知して非同期(fetch)で通信を行うようになります。
非同期通信というのは、簡単にいうと現在の画面の状態は変更せずに、バックグラウンドで通信を行う技術
です。Turbo Driveと組み合わせることで通信結果(レスポンス)が返ると画面に反映されます。
通常の画面遷移では通信中は画面を操作することができません。(現在はPCやブラウザの性能向上でそれほど体感できませんが。。)
Turbo Driveによるリクエストとレスポンス
通信結果のヘッダーとボディも確認してみましょう。
ここで変化があるのはリクエストヘッダー内のAcceptがtext/html, application/xhtml+xml
のみになっているところです。その他は特に変わりなく、レスポンスボディとしても完全なHTMLを返していることがわかります。
Turbo Driveまとめ
ここまでくれば本題のTurbo Driveの内容が理解できると思います。
🐱 Turbo DriveはTurbolinksの名前を変えたもので、基本的な機能はTurbolinksと同じだよ。リンク、フォームのリクエストをTurbo Driveがインターセプトして、fetchによる非同期リクエストに差し替える。そしてレスポンスされたHTMLの
<body>
要素だけを抜き出して、現在のページの<body>
要素を置換する。
置換の様子を図に表すと次の通りです。
ページ切り替えの非同期リクエストに対して、レスポンスされた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によるリクエストとレスポンス
先ほどと同様に通信の様子を確認してみましょう
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>
ページを切り替えの非同期リクエストに対して、レスポンスされた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を確認してみます。
先ほど記述したcats-list
下にid="cat_320"
などの独立したidが付与されたturbo-frame
がcatオブジェクト分生成されていることがわかります。
これは以下のように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
これを踏まえた上で編集ボタンをクリックした時の通信の様子を確認してみます。
確認できるポイントは次の通りです
- リクエストヘッダの
Turbo-Frame: cat_320
これによりTurbo Framesを使用したリクエストを送信している - レスポンスデータはレイアウトコンテンツを除いた
edit.html.erb
配下のコンテンツのみとなっている - レスポンスデータのフォームは
<turbo-frame id=”cat_320”>
で囲われており、置換先の要素を指定している
Turbo Framesによる置換をイメージでも確認してみましょう
編集ページの非同期リクエストに対して、レスポンスされた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に関しても同じように挙動を確認することができますので、ぜひ試してみてください!