LoginSignup
3
1

More than 1 year has passed since last update.

[Rails]Stimulus Content Loaderを使ってあとからコンテンツを読み込む

Last updated at Posted at 2021-04-30

はじめに

Railsでトップページや管理画面のダッシュボードなど、表示するコンテンツが多いページを作るときに1つのコントローラーで全てを取得するように作ると物によっては遅くなってしまうのではないでしょうか。
対策としてページ表示後にコンテンツを読み込むように Stimulusを使って実装してみようと思います。
こういった手法を遅延読み込み遅延ロードと言ったりします。英語だとlazy loadです。
よく画像の表示で使われたりします。

デモ

Image from Gyazo

ソースコードはこちら

実装

環境

  • Rails 6.1.3.1
  • Ruby 2.7.2
  • Yarn 1.22.5
  • Node 14
  • Docker

流れ

!!!注意!!!
今回Dockerを使っていますが、ここではDockerについては割愛しています。なのでdocker-composeなどのコマンドを省略しています。実際に再現する場合は各自の環境に合わせてコマンドや設定を変更してください。

今回Stimulusを使うのでwebpackでインストールします。

rails new my_app --webpack=stimulus

localhost:3000を確認します。

rails s

Ruby_on_Rails.png

この時点でapp/javascript配下に作成されたファイルを確認します。
rails newするときに--webpack=stimulusを指定したのでStimulusのcontrollerを読み込む設定が作成されています。

app/javascript/packs/application.js
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

Rails.start()
Turbolinks.start()
ActiveStorage.start()

import "controllers" // これ
app/javascript/controllers/index.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context("controllers", true, /_controller\.js$/)
application.load(definitionsFromContext(context))
app/javascript/controllers/hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "output" ]

  connect() {
    this.outputTarget.textContent = 'Hello, Stimulus!'
  }
}

まずはCRUD機能を作成していきます。
デモにあるように、今回は投稿とコメントの2つを作成して、投稿詳細ページを表示後にコメントを読み込むといった形を実装してみます。

$ rails g scaffold posts title body:text
$ rails g model comment post:references body:text
$ rails db:migrate

投稿詳細ページでコメントを閲覧・作成できるように変更します。

config/routes.rb
Rails.application.routes.draw do
  resources :posts do
    resources :comments, only: %i(index create destroy)
  end
end
app/models/post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy
end
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.new(comment_params)

    respond_to do |format|
      if @comment.save
        format.html { redirect_to @post, notice: "コメントしました" }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @comment.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @comment = @post.comments.find(params[:id])

    @comment.destroy
    respond_to do |format|
      format.html { redirect_to @post, notice: "コメントを削除しました" }
      format.json { head :no_content }
    end
  end

  private

  def set_post
    @post = Post.find(params[:post_id])
  end

  def comment_params
    params.require(:comment).permit(:body)
  end
end
app/views/posts/show.html.slim
p#notice = notice

p
  strong タイトル:
  p = @post.title
p
  strong 本文:
  = simple_format @post.body

=> link_to 'Edit', edit_post_path(@post)
'|
=< link_to 'Back', posts_path

/ 以下を追加

h4 コメント

- if @comments.present?
  ul
    - @comments.each do |comment|
      li
        = simple_format comment.body
        span = link_to "削除", [@post, comment], data: { confirm: 'Are you sure?' }, method: :delete
- else
  p コメントはありません。

hr

= form_with model: [@post, Comment.new] do |f|
  .field
    = f.label :body, "本文"
    br
    = f.text_area :body, required: true
  .actions = f.submit

ここまでで、投稿詳細ページでコメントできるようになりました。
ただ、コメントはページ時に取得しているので、これをページ表示後に取得するようにStimulusを使っていきます。

JSを書いていってもいいですが、以下のcomponentを使えばさくっと実装できそうなのでこれを使ってみます。

$ yarn add stimulus-content-loader

次にimportします。

app/javascript/controllers/index.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
import ContentLoader from "stimulus-content-loader" // 追加

const application = Application.start()
const context = require.context("controllers", true, /_controller\.js$/)
application.load(definitionsFromContext(context))
application.register("content-loader", ContentLoader) // 追加

コントローラーを変更します。
stimulus-content-loaderではページ表示後に非同期でコンテンツを取得するので、実行したいアクションを追加します。

app/controllers/comments_controller.rb
  # 省略...

  # 追加
  def index
    render partial: 'posts/comments', locals: { post: @post, comments: @post.comments }
  end

  # 省略...
app/views/posts/_comments.html.slim
- if comments.present?
  ul
    - comments.each do |comment|
      li
        = simple_format comment.body
        span = link_to "削除", [post, comment], data: { confirm: 'Are you sure?' }, method: :delete
- else
  p コメントはありません。
app/views/posts/show.html.slim
/ 省略...

h4 コメント
div[data-controller="content-loader" data-content-loader-url-value="#{ post_comments_path(@post) }"]
  p ローディング中...

/ 省略...

これでOKです。
実行してみるとデモのような動きになるはずです。

ログを確認してみます。
最初に投稿詳細ページを表示したあとに、コメントを取得しているのが分かります。

1__docker-compose_up.png

以上で完成です。
Stimulus Content Loaderが便利でJSを書かなくても実装できました。

よかったら試してみて下さい。

補足

  • Stimulus Content Loaderにはdata-content-loader-refresh-interval-valueを指定することで、表示したコンテンツをN秒ごとに再ロードする設定ができるようです。
  • ソースコードも公開されているのでJSの勉強にも使えそうです。今回のStimulus Content Loaderのコードはこちら
  • Stimulus Componentsには、他にも多くのcomponentがあるので便利そうです。
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1