TurboStreamで作る超簡単な無限スクロール
弊社ではインタラクティブではない画面を対象に、
RailsとVueを混在したMPAから純粋なRailsのコードへの移行を検証しています。
試しに1画面の置き換えを行う過程で簡単に無限スクロールのUIを実装できたので、共有します。
TurboStreamで作る無限するスクロール
技術仕様等
Rails 7.0以上で動作します。またページネーションにはKaminariを利用しています。
ブラウザバック時にページコンテンツとページ位置は保持されます。
難しいことがやりたい人は下記記事を参考にして、難しい無限スクロールを実装してみてください。
Controller
今回はTurboStreamを使ってページネーションを実装するので、下記のように実装します。
format.turbo_streamがある以外は普通のRailsのControllerですね。
class UsersController < ApplicationController
before_action :set_user, only: %i[ show edit update destroy ]
def index
params[:page] ||= 1
params[:limit] ||= 30
@users = User.page(params[:page]).per(params[:limit])
respond_to do |format|
format.html
format.turbo_stream
end
end
end
View
ここで、users/index.html.erbを実装します。
<p style="color: green"><%= notice %></p>
<h1>Users</h1>
<div id="users">
<% @users.each do |user| %>
<%= render user %>
<p>
<%= link_to "Show this user", user %>
</p>
<% end %>
</div>
<% if @users.next_page %>
<%= turbo_frame_tag "load_more", src: users_path(page: @users.next_page, format: :turbo_stream), loading: :lazy %>
<% end %>
<%= link_to "New user", new_user_path %>
users/_user.html.erbはこんな感じ。
<div id="<%= dom_id user %>">
<p>
<strong>Name:</strong>
<%= user.name %>
</p>
</div>
上記で、turbo_frame_tag
で次のページのFrameをturbo_frame_tagの"load_more"が画面に表示されるタイミングで src
にあるURLの内容を読み込みます。
users/index.turbo_stream.erb
<%= turbo_stream.append "users" do %>
<% @users.each do |user| %>
<%= render user %>
<p>
<%= link_to "Show this user", user %>
</p>
<% end %>
<% end %>
<%= turbo_stream.replace "load_more" do %>
<% if @users.next_page %>
<%= turbo_frame_tag "load_more", src: users_path(page: @users.next_page, format: :turbo_stream), loading: :lazy %>
<% end %>
<% end %>
上記のようにHTML IDの'users'にturbo_streamを経由してロードしたHTMLの内容をAppendしています。またページネーションについては、turbo_streamでreplaceを行うことで、次に読み込むべきTurboFrameに更新しています。
TurboFrameが入れ子にならない
ざっと調べた感じ、TurboFrameが入れ子になってしまうパターンだったり、
TurboFrameが入れ子にならない方法でも、それなりに複雑な処理が必要な方法がメインでした。
こちらの方法ではHTML要素の構造自体も実装もシンプルにまとまりました。
とはいえTurboStreamの多様は危険
複雑なTurboStreamレスポンスは、コードの理解を難しくするので、
一つのTurboStreamのViewの中で多くても許されるのはせいぜい3つの要素の操作くらいだと思います。なので、これ以上TurboStreamでの操作が増えそうということであれば、JSでの実装を検討していいと思っています。
DHHはどう実装しているか?
最後に答え合わせとしてDHHが実際にPaginationで使用していると思われるgistを紹介します。
ここではTurboStreamを使わないで、単純にJSで実装されてるようでした。
サーバーサイドのTurboStreamよりも、フロントエンドでのコントローラーのほうが再利用しやすいと考えられます。
コードはJSで実装するほうが長いですが、個別にTurboStreamの処理をサーバーサイドを書いたりする必要はなくなると思うので、好きな方を選択するようにしましょう。
で、どうしたの?
上記DHHの実装をベースにWebComponentsで再実装したものを利用することにしたのでした。
まとめ
このように簡単な無限スクロールもTurboでサクッとできてしまいました。
無限スクロールでも簡単な要件の場合もあると思うのでぜひ試してみてください。
Discussion