Commentary
詳細パネル
タブメニューのページでTurbo Framesを使ってタブメニューを実装しました。ここではTurbo Framesの別の使い方を紹介します。違いはページのどこを部分置換するかです。
Turboはfetch
でサーバにリクエストを飛ばし、レスポンスを使ってページの部分置換をするライブラリです。Turbo Drive、Turbo Frames、Turbo Streamsの3種類がありますが、置換場所をどのように指定するかが異なります。
タブメニューでは、"Turbo Frames" > "トリガーを囲むturbo-frame
タグ"を置換する 仕組みを使いました。今回の詳細パネルでは、"Turbo Frames" > "任意のturbo-frame
タグ"を置換する 仕組みを使用します。
技術 | 置換場所 | 注記 |
---|---|---|
Turbo Drive | body タグ全体 | |
Turbo Frames | トリガー※ を囲むturbo-frame タグ | |
任意のturbo-frame タグ | a タグやform タグのdata-turbo-frame 属性で指定する | |
Turbo Streams | id で指定した任意の要素 | レスポンス中のturbo-stream タグのid 属性で対象要素を指定する |
a
タグもしくはform
タグになります。またGETでかつTurbo Streams以外であればJavaScriptからTurbo.visit()を呼び、Turboを稼働することも可能です。JavaScriptでGET以外、もしくはTurbo Streamのリクエストを飛ばしたい場合はrequest.jsライブラリを使うのが便利です。下図のように、タブメニューの場合は、turbo-frame
がクリックするa
タグ(タブ)を囲んでいました。a
タグをクリックした時、それを包む最も近いturbo-frame
を探し出し、そのturbo-frame
を部分置換すると判断したのです。
今回はa
タグにdata-turbo-frame
属性を持たせ、どのturbo-frame
を部分置換するかを明示的に指定します。コードは下記のようになります。
特にuser-detail-frame
を使って、a
タグと詳細パネルを繋げていることに注目してください。
こうすることで、a
タグのリンク先から返されるHTMLからuser-detail-frame
の箇所が切り取られ、詳細パネルのturbo-frame
の内部を置換します。
templates/details_panel/index.ejs のa
タグ周り
<a href="<%= `/api/hotwire/details_panel/user?id=${user.id}` %>"
data-turbo-frame="user-detail-frame"
class="block w-full h-full py-4 pl-4 pr-4 sm:pl-0"
onclick="highlight({active: '#user-<%= user.id %>', inactive: '.user-row'})"
>
<%= user.name %>
</a>
templates/details_panel/index.ejs の詳細パネル周り
<div class="mt-8 border p-4 rounded min-h-44">
<turbo-frame id="user-detail-frame"
class="turbo-with-loader">
<div class="turbo-hide-on-loading"></div>
</turbo-frame>
</div>
詳細パネルをReact/Next.jsで実装したコードはpages/details_panel/index.tsx
をご覧ください。
selectedUser
ステートが更新されますUserDetailPanel
コンポーネントにpropsとして渡されますUserDetailPanel
コンポーネントでは受け取ったpropsを使い、useEffect
の中で詳細パネルのデータをサーバからfetch
して、ユーザ詳細を描画します。Hotwire版もReact版もコードは比較的シンプルで、難しいところはありません。どちらかというとHotwireの方が簡単だと思いますが、Reactを使い慣れている人にとってはReact版も十分に簡単でしょう。
今回、table
の中で選択した行をハイライトしています。React版の場合は同じselectedUser
ステートを使いまわせますが、Turboだけだとこれを保持しませんので、改めてonclick
でJavaScriptを稼働させてCSSクラスを更新する必要があります。なお通常はHotwireと相性の良いStimulusを使うと思いますが、今回は簡便性を優先してインラインでJavaScriptを書きました。Stimulusは後で紹介していきます。
Turboだけだったら追加のJavaScriptを一切書かなくても良かったのが、UXを改善しようと思うとやはり書く必要があるわけです。Turboだと変に甘えてしまって、このちょっとした改善を怠ることがありがちです。「Turboで楽をさせてもらった分はUXを頑張ろう!」ぐらいの気持ちでいた方が良いと思います。
Hotwire版はa
タグを使ってTurbo Framesを稼働させているため、prefetchが動きます。マウスホバー時にフライングし、クリックする前に詳細パネルのデータを読み込むので、体感速度は大幅に向上します。これについてはページ遷移のUXで解説しています。なおレスポンスに1秒以上かかるページの場合、Turbo Frameでもローダーを表示するべきです。詳細については別途解説しますが、今回はCSSだけで実装しています。
一方でReact版はuseEffect
を使いますので、Next.jsのprefetchが効きません。クリックしてから詳細パネルのデータを読み込みますので、ネットワーク遅延、サーバ処理時間の分だけユーザは待たされます。ローダーは出していますので無反応というわけではないのですが、必要なデータが表示されるまでは待つことになります。
この分だけ、Hotwire版の方がヌルサクなUXになります。