はじめに
こんにちは、フロントエンドエンジニアの Okuma です。
ここ数ヶ月はバックエンドの開発をサポートする中でビューテンプレートを触る機会が非常に多くありました。
今回は、Ruby on Railsに標準搭載されているJavaScriptフレームワーク、Stimulusについて、その基本的な考え方と使い方をまとめてみました。特に、Stimulusの強力な機能であるライフサイクルコールバックの中から、connectとdisconnectに焦点を当てて、具体的なコード例と共にその役割を掘り下げていければと思います。
「RailsでちょっとリッチなUIを作りたいけど、ReactやVueのような大規模なフレームワークを導入するのは大げさだ…」と感じている方にぴったりの内容です!
Stimulusとは? - "HTML-first"なJavaScript -
Stimulusは、Hotwire(HTML-Over-the-Wire)というアプローチの一部として、Rails 7から標準で採用されているJavaScriptフレームワークです。
その最大の特徴は、「すでにあるHTMLをJavaScriptで装飾する」 という思想にあります。多くのフレームワークがJSONをやり取りしてフロント側でHTMLを生成するのとは対照的に、Stimulusはサーバーが生成したHTMLにdata-*属性を少し書き加えるだけで、特定の振る舞いを付与できます。
このアプローチにより、以下のようなメリットが生まれます。
- 学習コストが低い: 覚えるべき概念が少なく、既存のRailsの知識を活かせます。
- Railsとの親和性: サーバーサイドのロジックに集中しつつ、必要な箇所だけリッチな体験を提供できます。
- コードの見通しが良い: HTMLを見れば、どのJavaScriptが動作するのかが一目瞭然です。
Stimulusの基本的な使い方
それでは、Stimulusの基本的な使い方を見ていきます。
①コントローラーの作成
Stimulusでは、特定の振る舞いを「コントローラー」という単位で管理します。
以下のコマンドを実行して、新しいコントローラーを作成してみましょう。
rails g stimulus hello
このコマンドにより、app/javascript/controllers/hello_controller.jsというファイルが生成されます。
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="hello"
export default class extends Controller {
connect() {
console.log("Hello, Stimulus!", this.element);
}
}
②HTMLとの接続
作成したコントローラーをHTML要素と結びつけるには、data-controller属性を使います。ファイル名のケバブケース(hello_controller.js → hello)が識別子になります。
<div data-controller="hello">
<h1 class="text-2xl">Hello Stimulus!</h1>
</div>
このHTMLがブラウザで読み込まれると、Stimulusはdata-controller="hello"を検知し、hello_controller.jsのインスタンスを生成してこの<div>要素にアタッチします。そして、後述するconnectメソッドが自動的に実行され、コンソールにメッセージが出力されます。
「Stimulusのコントローラを用意して、data-controller属性と結びつける」これだけでセッティングは完了なのでとても簡単だと思います。
ライフサイクルコールバック:connectとdisconnect
Stimulusのコントローラーには、特定のタイミングで自動的に呼び出されるライフサイクルコールバックという便利なメソッドがあります。今回はその中でも最も重要なconnectとdisconnectを解説します。
私はStimulusの実装を初めてした当初、connectメソッドの存在はドキュメントなどを見てなんとなく掴んでいましたが、disconnectの取り扱いについてはフロントチームのレビューから指摘されて初めて知りました。
connect() - 接続されたとき
connect()は、コントローラーがHTML要素に接続された(アタッチされた)直後に一度だけ実行されます。
-
実行タイミング:
- ページが読み込まれ、
data-controller属性を持つ要素がDOMに現れたとき - Turbo Streamsなどによって、
data-controller属性を持つ要素が動的にページに追加されたとき(すみません、今回はTurbo Streamsの説明は割愛させていただきます🙇🏻♂️)
- ページが読み込まれ、
-
主な用途:
- イベントリスナーの設定(クリック、入力など)
- 外部ライブラリの初期化(グラフ描画、日付ピッカーなど)
- タイマーの開始
- 初期状態の設定
disconnect() - 切断されたとき
disconnect()は、コントローラーがHTML要素から切断される(デタッチされる)直前に一度だけ実行されます。
-
実行タイミング:
- ページ遷移が発生するとき(そのページから離脱するとき)
- Turbo Streamsなどによって、
data-controller属性を持つ要素が動的にページから削除されたとき
-
主な用途:
- クリーンアップ処理、
connectで行った設定を元に戻す作業 -
connectで追加したイベントリスナーの削除(この項目に対してはレビュー指摘をいただきました) - 実行中のタイマー(
setInterval)の停止 - メモリリークの防止(
disconnectの実行での効果についてもレビュー指摘で知りました)
- クリーンアップ処理、
disconnectを適切に実装することで、不要な処理がバックグラウンドで動き続けることを防ぎ、アプリケーションのパフォーマンスを健全に保つことができます。
実践例:通知を自動更新するコンポーネント
connectとdisconnectの強力な連携を理解するために、簡単な例として**「新しい通知があるか5秒ごとにサーバーに問い合わせる」** コンポーネントを作成してみます。
①通知コントローラーの作成
まず、notificationsコントローラーを作成します。
rails g stimulus notifications
②ビューの実装
次に、ビューにdata-controllerを記述します。
未読件数を表示するための<span>要素には、後でコントローラーから参照できるようにdata-notifications-target="count"という属性を追加しておきます。
<div data-controller="notifications">
<p>未読の通知: <span data-notifications-target="count">...</span> 件</p>
</div>
③コントローラーの実装
notifications_controller.jsを以下のように編集します。
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="notifications"
export default class extends Controller {
// `data-notifications-target="count"` を持つ要素を this.countTarget で参照できるようにする
static targets = ["count"];
connect() {
// 5秒ごとに refresh メソッドを実行するタイマーを開始
// this.timer にタイマーのIDを保存しておく
this.timer = setInterval(() => {
this.refresh();
}, 5000);
console.log("通知pollingを開始しました。");
}
disconnect() {
// このコントローラーがDOMから削除されるときにタイマーを停止
clearInterval(this.timer);
console.log("通知pollingを停止しました。");
}
refresh() {
// ここではダミーの数値を生成するが、
// 実際にはfetch APIなどを使ってサーバーに問い合わせる
const newCount = Math.floor(Math.random() * 10);
this.countTarget.textContent = newCount;
console.log(`通知件数を更新: ${newCount}`);
}
}
このコードのポイント
-
connect(): このコンポーネントがページに表示されると、setIntervalが実行され、5秒ごとにrefreshメソッドが呼ばれるようになります。 -
disconnect(): ユーザーが別のページに遷移するなどして、このコンポーネントがDOMから削除されると、clearIntervalが実行され、タイマーが確実に停止します。もしdisconnectでの停止処理がないと、ページ遷移後もバックグラウンドで無駄な処理が永遠に実行され、パフォーマンスの低下や予期せぬエラーの原因になってしまいます。
このようにconnectで処理を開始し、disconnectで後片付けをする、というのがStimulusにおける非常に重要で美しいパターンです。
まとめ
今回は、Railsの相棒であるStimulus.jsの基本的な使い方と、ライフサイクルコールバックconnect/disconnectの重要性について解説しました。
- StimulusはHTML中心のシンプルなJavaScriptフレームワークである
-
connect()は要素の初期化やイベント設定の「開始地点」 -
disconnect()は後片付けやリソース解放のための「終了地点」
この「開始」と「終了」のペアを意識することで、クリーンでメモリ効率の良い、信頼性の高いフロントエンドコードを書くことができます。
Rails開発に、ぜひStimulusを取り入れてみてはいかがでしょうか?
エンジニア募集中
Gakken LEAPは日々、教育のアップデートに取り組んでいます!
興味をお持ちいただいた方は、採用サイトをチェックしてみてください!