1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails 8でのJavaScript連携まとめ:バニラJSとStimulusの比較

Posted at

目次

1.はじめに
2.バニラJSでの実装
3.Stimulusでの実装
4.バニラJSとStimulusの比較
5.まとめ

1. はじめに

本記事は、Rails8でのJavaScript連携方法をまとめた記事となっております。
HTMLとJavaScriptの連携方法を フレームワークを使わない素のJS(バニラJS)Stimulusの両方で実装して比較します。

学習ログを兼ねつつ、これからRailsでJSを書く人の参考になることを目的としています。

2. バニラJSでの実装

2-1. ファイルの置き場所

自作のJavaScriptファイルを app/javascript/ に置きます。

例:app/javascript/hamburger.js

hamburger.js

//app/javascript/hamburger.js

document.addEventListener("turbo:load", () => {
  const btn = document.getElementById("hamburger-btn");
  const nav = document.getElementById("header__nav");
  if (!btn || !nav) return;

  btn.addEventListener("click", () => {
    const expanded = btn.getAttribute("aria-expanded") === "true";
    btn.setAttribute("aria-expanded", String(!expanded));
    btn.classList.toggle("is-active");
    nav.classList.toggle("is-open");
  });

  nav.addEventListener("click", () => {
    nav.classList.remove("is-open");
    btn.classList.remove("is-active");
    btn.setAttribute("aria-expanded", "false");
  });
});

2-2. レイアウトでJSを読み込む

app/views/layouts/application.html.erbhead 内に以下を記述します。

application.html.erb
<head>
  <%= javascript_importmap_tags %>
</head>

Rails 7 以降は Importmap を通してJSを読み込む仕組みになっています。

2-3. importmap の設定

config/importmap.rb にファイルを登録します。

importmap.rb
# config/importmap.rb
pin "hamburger", to: "hamburger.js"

Importmapとは、ブラウザに対して「どのモジュール名がどのファイルに対応しているか」を教える仕組みです。

この1行で、ブラウザは import "hamburger" と書かれたときに、app/javascript/hamburger.js を読み込むようになります。

2-4. エントリで読み込む

最後に app/javascript/application.js で読み込みます。

application.js
// app/javascript/application.js
import "hamburger"

app/javascript/application.js は、Rails アプリにおける JavaScript のエントリーポイントです。

このように書くと先ほど importmap.rb で設定した "hamburger" → "hamburger.js" の対応が適応され、app/javascript/hamburger.js が読み込まれます。

これで、Railsアプリ内で hamburger.js が動作するようになります。

3. Stimulusでの実装

3-0. Stimulusとは?

Stimulusは Rails公式で推奨される軽量JSフレームワークです。
特徴は以下の通り:

  • HTMLのdata-*属性と連携する仕組み
  • コントローラ単位でコードを分割できる
  • Turboと相性が良く、ページ遷移時も自動で再接続される

3-1. ファイルの置き場所

Stimulusを用いる場合、JavaScriptはapp/javascript/controllers/にファイルを作成して記述します。

app/javascript/
├─application.js
└─controllers/
     ├─index.js
     └─hamburger_controller.js   ←作成したファイル

例:app/javascript/controllers/hamburger_controller.js

hamburger_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["button", "nav"]

  toggle() {
    this.buttonTarget.classList.toggle("is-active")
    this.navTarget.classList.toggle("is-open")
  }

  close() {
    this.buttonTarget.classList.remove("is-active")
    this.navTarget.classList.remove("is-open")
  }
}

3-2. レイアウトでJSを読み込む

2-2と同様に、app/views/layouts/application.html.erbhead 内に以下を記述します。

application.html.erb
<head>
  <%= javascript_importmap_tags %>
</head>

3-3. importmap の設定

config/importmap.rb にファイルを登録します。

importmap.rb
# config/importmap.rb
pin "@hotwired/stimulus",          to: "stimulus.min.js"
pin "@hotwired/stimulus-loading",  to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"

pin_all_from "app/javascript/controllers", under: "controllers"
のように書くことで、app/javascript/controllers下のファイルを自動的に紐づけてくれます。

例えば、app/javascript/controllers/hamburger_controller.jsというファイルがある場合、以下と同じ意味になります。

importmap.rb
# config/importmap.rb
pin "hamburger_controller", to: "hamburger_controller.js"

3-4. エントリ(application.js)と登録方法

ここまでで config/importmap.rb に Stimulus と pin_all_from を設定し、レイアウトに <%= javascript_importmap_tags %> を入れました。
最後にエントリ(app/javascript/application.js)で Stimulus を起動し、コントローラを登録します。

application.js
import { Application } from "@hotwired/stimulus"

const application = Application.start()

application.debug = false
window.Stimulus   = application


export { application }

4. バニラJSとStimulusの比較

4-1. バニラJSとHTMLの連携

バニラJSでは、DOM探索をgetElementByIdなどを用いて行う必要があります。ハンバーガーボタンの実装を例に解説します。

JavaScript側hamburger.js

hamburger.js
document.addEventListener("turbo:load", () => {
  const btn = document.getElementById("hamburger-btn");
  const nav = document.getElementById("header__nav");
  if (!btn || !nav) return;

  btn.addEventListener("click", () => {
    const expanded = btn.getAttribute("aria-expanded") === "true";
    btn.setAttribute("aria-expanded", String(!expanded));
    btn.classList.toggle("is-active");
    nav.classList.toggle("is-open");
  });

  nav.addEventListener("click", () => {
    nav.classList.remove("is-open");
    btn.classList.remove("is-active");
    btn.setAttribute("aria-expanded", "false");
  });
});

HTML側application.html.erb

application.html.erb
<!-- ハンバーガーボタン -->
<button
  id="hamburger-btn"
  class="hamburger-btn"
  aria-expanded="false"
  aria-controls="header__nav"
>
  <span class="hamburger-bar"></span>
  <span class="hamburger-bar"></span>
  <span class="hamburger-bar"></span>
</button>

このようにバニラJSでは、buttonタグにidを設定した上でbtn.getAttribute("")のように受け取る必要があります。

4-2. StimulusとHTMLの連携

Stimulusでは、JavaScriptとHTMLの連携を data-controller と data-action を使って行います。
これにより、DOM探索を自分で書かずに、HTMLの属性として紐づけるだけでイベントを処理できます。
JavaScript側hamburger_controller.js

hamburger.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["button", "nav"]

  toggle() {
    this.buttonTarget.classList.toggle("is-active")
    this.navTarget.classList.toggle("is-open")
  }

  close() {
    this.buttonTarget.classList.remove("is-active")
    this.navTarget.classList.remove("is-open")
  }
}

HTML側application.html.erb

application.html.erb
<!-- ハンバーガーボタン -->
<div data-controller="hamburger">
  <button
    data-hamburger-target="button"
    data-action="click->hamburger#toggle"
    class="hamburger-btn"
    aria-expanded="false"
    aria-controls="header__nav"
  >
    <!-- ボタン表示 -->
  </button>

  <nav id="header__nav" data-hamburger-target="nav">
    <!-- ナビゲーション内容 -->
  </nav>
</div>

Stimulusでは data-* 属性を付与するだけで要素とコードが結びつきます。

4-3. バニラJSとStimulusを使ってみた感想

バニラJSは、getElementById や querySelector を使ってDOMを取得する方法が直感的で、初心者の私でも迷わず実装することができました。ただしイベントが増えていくと addEventListener が多くなり、コードが複雑に見えてしまう点が気になりました。

一方、Stimulusを使ったときは、data-controller や data-action といった仕組みによって、HTMLと自然に結びつけられるのが便利でした。コントローラごとにコードが分かれているため、後から見返したときにも「どの部分が何を担当しているのか」がすぐに分かります。

全体を通してみると、ちょっとした動きを追加する程度ならバニラJSで十分ですが、アプリケーションを育てていくのであればStimulusを選んだ方が保守や拡張が楽になるというのが率直な感想です。Rails 7以降では公式にStimulusが推奨されていることもあり、迷ったときはStimulusを使うのが無難だと感じました。

まとめ

  • Rails 8 では Importmap により JS を簡単に読み込める
  • バニラJSは簡単な実装向きだが、複雑になるとコードが煩雑になりやすい
  • Stimulusは公式推奨で、拡張性・保守性に優れる
  • 迷ったら Stimulus を選ぶのが無難
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?