はじめに
本記事は錆びかけたRailsの知識を頑張ってアップデートするアドベントカレンダー12日目です。
Turboを使うと、無限スクロールの実装が驚くほど簡単にできます。
無限スクロールとは
ある程度スクロールすると続きのコンテンツがロードされるようなUIです。
一番馴染み深いのはTwitter(X)のアプリの挙動です。タイムラインをスクロールしていくと、自動で続きが読み込まれますね。
Turboを利用した無限スクロールの実装を見た時、あまりの簡単さに感動したので、やり方をチュートリアル形式でごく簡単に紹介します。
対象読者
Ruby on Railsを学習中の人
Railsですでに簡単なアプリケーションを作成したことがある人
※まだRailsについてよくわかっていないよ、という方は対象ではありません。
事前準備
以下の環境で実装を行います。必要に応じて環境構築してください。
- Ruby 3.2.0
- Rails 7.0.5
- MySQL 8系
- OS: MacOS Monterey バージョン 12.6.2
以下の記事はDockerで上記の環境を作る記事となっています。
Docker for MacをインストールしてRails7 + MySQL8の環境を作る
ここでは、Rails7系でrails new
して、アプリの雛形ができていると想定します。Rubyの該当バージョンやRails7系がインストールできていれば
ローカルサーバの立ち上げ方に注意
Railsはこれまでrails s
コマンドを使ってローカルサーバを立ち上げていました。Rails7からは、bin/dev
というコマンドで立ち上げるようになっています。
bin/dev
というコマンドについては以下の記事を参照してください。
bin/devって何だ
bin/devコマンドを使ってローカルサーバを立ち上げ、Google Chromeなどのブラウザからlocalhost:3000
にアクセスして以下のように表示されていれば準備OKです。
Turboによる無限スクロール体験ハンズオン
作るもの
手順
tweetモデルとテーブルを作成する
tweetsテーブルにダミーデータを100件投入する
一覧ページでtweet一覧を100件表示する
ページネーションを実装する
無限スクロールを実装する
tweetモデルとテーブルを作成する
まずは、無限スクロールを試すリソースを用意します。今回はtweetというモデルとし、表示する中身はcontentという属性値にします。以下のコマンドで、モデルとマイグレーションファイルを作成します。
docker compose run i-scroll-web rails g model tweet content:string
続いてマイグレーションファイルを読み込みテーブルを作成します。
docker-compose run i-scroll-web rails db:migrate
tweetsテーブルにダミーデータを100件投入する
tweetsテーブルを作成できたので、ダミーデータを投入します。
今回はgem faker
を使います。
// Gemfileの一番下に追記
gem "faker"
bundle installします。
docker-compose run i-scroll-web bundle
seeds.rbの中身を以下のように書き換えます。
require 'faker'
100.times do |i|
Tweet.create(content: Faker::Lorem.sentence(word_count: 5), nickname: Faker::Internet.username)
end
作成したseeds.rbを実行します。これで、tweetsテーブルにレコードが100件保存されます。
docker-compose run i-scroll-web rails db:seed
一覧ページでtweet一覧を100件表示する
続いて一覧ページを作成します。コントローラやviewはscaffold
で作成します。
rails g scaffold_controller Tweet
これで、必要なルーティング/コントローラ/ビューの準備が整います。以下のようなファイルが作成されていればOKです。
手動で作成しても構いません。
Rails.application.routes.draw do
resources :tweets
end
class TweetsController < ApplicationController
before_action :set_tweet, only: %i[ show edit update destroy ]
# GET /tweets
def index
@tweets = Tweet.all
end
# 以下略
end
<p style="color: green"><%= notice %></p>
<h1>Tweets</h1>
<div id="tweets">
<% @tweets.each do |tweet| %>
<%= render tweet %>
<p>
<%= link_to "Show this tweet", tweet %>
</p>
<% end %>
</div>
<%= link_to "New tweet", new_tweet_path %>
<div id="<%= dom_id tweet %>" style="padding:20px 0">
<%= tweet.content %>
</div>
ページネーションを実装する
続いてページネーションを実装します。ページネーションとは、表示すべきコンテンツが多すぎる際に一定の数で区切り、続きを見る際は「2,3,4・・・」のようなリンクを押す形のUIです。
ページネーションはgem kaminari
で実装します。Gemfileに以下のように追記します。
# 下部に追記
gem "faker"
gem 'kaminari'
bundle installします。
docker-compose run i-scroll-web bundle
tweets_controller.rbのindexアクションの中身を、kaminari
の仕様にそった形に書き換えます。
class TweetsController < ApplicationController
before_action :set_tweet, only: %i[ show edit update destroy ]
# GET /tweets
def index
@tweets = Tweet.order(created_at: :desc).page(params[:page]).per(40)
end
# 以下略
end
これで、一覧ページの最初の表示時には40件しかツイートが表示されないことになります。
無限スクロールを実装する
ここが肝です。
無限スクロールには、Turbo Frame
を利用します。
<div class="tweet-page" style="padding:10px 40px">
Tweets
<%# 今のページの`<turbo-frame>` %>
<%= turbo_frame_tag "tweets-page-#{@tweets.current_page}" do %>
<%# 今のページで取得したTweet一覧 %>
<%= render @tweets %>
<%# 遅延読み込みで次ページを取得する`<turbo-frame>` %>
<%= turbo_frame_tag "tweets-page-#{@tweets.next_page}", loading: :lazy, src: path_to_next_page(@tweets) %>
<% end %>
</div>
これだけです。
たったのこれだけで、tweet一覧画面が無限スクロールになります。
書く量が少なすぎる。。。
なぜ無限スクロールになるのか
書き換えたindex.html.erb
のポイントは以下の通りです。
turbo_frame_tag ~ do
で<render @tweets>
を囲った
turbo_frame_tag
のloading: :lazy
オプションで遅延読み込みを行った
turbo_frame_tag ~ do ~ end
で<render @tweets>
を囲った
turbo_frame_tag ~ do ~ end
で囲った部分は、リクエストがTurboのリクエストとなります。
Turboのリクエストは非同期になり、ページを切り替えません。
その時リクエストするのが、次節で説明するloading: :lazy
がついたturbo_frame_tag
の読み込みです。
turbo_frame_tag
のloading: :lazy
オプションで遅延読み込みを行った
turbo_frame_tagでこのオプションがついている場合、の要素が読み込まれたタイミングでsrc
へのリクエストをします。
今回、kaminariを使ったpaginationのオプションを利用して次のページの番号を取ったり、次のページを取るためのリクエストURLを準備しています。