Top
beta

Commentary

Details Panel

詳細パネル

タブメニューのページでTurbo Framesを使ってタブメニューを実装しました。ここではTurbo Framesの別の使い方を紹介します。違いはページのどこを部分置換するかです。

別のTurbo Frameを更新するパターン #

Turboはfetchでサーバにリクエストを飛ばし、レスポンスを使ってページの部分置換をするライブラリです。Turbo Drive、Turbo Frames、Turbo Streamsの3種類がありますが、置換場所をどのように指定するかが異なります。

タブメニューでは、"Turbo Frames" > "トリガーを囲むturbo-frameタグ"を置換する 仕組みを使いました。今回の詳細パネルでは、"Turbo Frames" > "任意のturbo-frameタグ"を置換する 仕組みを使用します。

Turboはどこを置換するか?
技術置換場所注記
Turbo Drivebodyタグ全体
Turbo Framesトリガー を囲むturbo-frameタグ
任意のturbo-frameタグaタグやformタグのdata-turbo-frame属性で指定する
Turbo Streamsidで指定した任意の要素レスポンス中の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.ejsaタグ周り

<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との比較 #

コードの簡潔さ

詳細パネルをReact/Next.jsで実装したコードはpages/details_panel/index.tsxをご覧ください。

  1. ユーザの行をクリックした結果、selectedUserステートが更新されます
  2. このステートがUserDetailPanelコンポーネントにpropsとして渡されます
  3. 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になります。