better-stimulus について
better-stimulusは、Stimulusコントローラーの作成、リファクタリング、レビューを支援するスキルです。設定、状態管理、ライフサイクル、コントローラー間通信の指針をまとめています。
主な機能
- 01data属性によるコントローラー設定
- 02Values APIによる状態管理
- 03単一責任の原則の適用
- 04宣言的なイベント登録
- 05コントローラー間通信とTurboのライフサイクル対応
活用シーン
- 01Stimulusコントローラーの新規作成
- 02複数の関心事を持つコントローラーの整理
- 03コントローラー間通信のデバッグ
- 04サードパーティJavaScriptライブラリやTurboの統合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ obie/ skills --skill better-stimulusインストール後、Claude Code で/better-stimulusを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/architecture.mddom-and-turbo.mderror-handling.mdevents-and-interaction.mdlifecycle.mdsolid-principles.mdstate-management.md詳細
Better Stimulus
Stimulus コントローラーの作成やリファクタリング時に、betterstimulus.com の推奨プラクティスを適用します。これらのパターンでは、コードの再利用性、適切な関心事の分離、SOLID 設計原則を重視します。
このスキルを使用するタイミング
次の場合にこのスキルを呼び出します:
- 新しい Stimulus コントローラーを作成するとき
- 既存の Stimulus コードをリファクタリングするとき
- Stimulus コントローラーのアーキテクチャをレビューするとき
- コントローラー間の通信をデバッグするとき
- サードパーティの JavaScript ライブラリを統合するとき
- フォーム送信のロジックを実装するとき
- コントローラーの状態を管理するとき
- Turbo との統合を設定するとき
コア原則
1. コントローラーを設定可能にする
コントローラーのロジックに値を埋め込むのではなく、ハードコードされた値をデータ属性に外部化します。
悪い例:
toggle() {
this.element.classList.toggle("active")
}
良い例:
static classes = ["active"]
toggle() {
this.element.classList.toggle(this.activeClass)
}
<div data-controller="toggle" data-toggle-active-class="active"></div>
2. 状態には Values API を使用する
リアクティブ機能と DOM への永続化を活用できるよう、コントローラーの状態はインスタンスプロパティではなく Stimulus の値に保存します。
悪い例:
connect() {
this.count = 0
}
良い例:
static values = { count: Number }
countValueChanged(count) {
this.updateDisplay()
}
3. コントローラーの責務を絞る(単一責任)
各コントローラーが変更される理由は1つにします。 複数の関心事が混在するコントローラーは分割します。
「このコントローラーが変更されるのはどんなときか?」と考えます。互いに関係のない理由が複数ある場合は、分割します。
4. connect() を使いすぎない
connect() を使用する対象:
- サードパーティのプラグイン(Swiper、Chart.js など)のインスタンス化
- 機能やブラウザーの機能の検出
次の用途では connect() を使用しないでください:
- 状態の設定(Values API を使用します)
- イベントリスナーの追加(
data-actionを使用します)
5. イベントを宣言的に登録する
Stimulus にライフサイクルを管理させるため、addEventListener() ではなく data-action 属性を使用します。
悪い例:
connect() {
document.addEventListener("click", this.handler.bind(this))
}
良い例:
<div data-action="click@document->controller#handler"></div>
主なパターン
アーキテクチャ
- 設定可能なコントローラー:データ属性を介して依存関係を注入します
- Application Controller:共有機能の基底クラスです
- ミックスイン:「~として振る舞う」という関係を介して動作を共有します
- ターゲットレスコントローラー:要素の操作とターゲットの操作を分離します
- 名前空間付き属性:任意のパラメーターセットを処理します
参照:references/architecture.md
状態管理
- ほぼすべての状態に Values API を使用します
- 変更コールバック(
[name]ValueChanged)を活用します - 値はシリアル化可能な状態に保ちます
- 妥当なデフォルト値を設定します
参照:references/state-management.md
ライフサイクル
- サードパーティライブラリの初期化には
connect()を使用します - クリーンアップのために
connect()とdisconnect()を組み合わせます - 状態の設定を
connect()に詰め込みすぎないようにします - Turbo 固有のクリーンアップには
teardown()を実装します
参照:references/lifecycle.md
コントローラー間の通信
アプローチは3つあります:
- カスタムイベント:疎結合、ブロードキャストパターン
- Outlets:コントローラーへの直接参照、構造化されたレイアウト
- コールバック:他のコントローラーから状態を取得
関係性に応じて選択します:
- 受信側が不明 → カスタムイベント
- 階層が明確 → Outlets
- データ共有 → コールバック
参照:references/events-and-interaction.md
SOLID 原則
- 単一責任:変更される理由を1つにする
- オープン・クローズド:変更ではなく継承によって拡張する
- 依存性逆転:抽象に依存し、設定を介して注入する
参照:references/solid-principles.md
DOM と Turbo
- DOM の状態を復元するには
<template>を使用します - フォームでは
submit()ではなくrequestSubmit()を使用します - Turbo のキャッシュに備えてグローバルなティアダウンを実装します
- Turbo イベントを宣言的に処理します
参照:references/dom-and-turbo.md
エラー処理
handleError()メソッドを備えた ApplicationController を作成します- エラートラッキング(Sentry、Honeybadger)と統合します
- ユーザーに分かりやすいメッセージを提供します
- 非同期処理には try-catch を使用します
参照:references/error-handling.md
クイックリファレンス
値の型
static values = {
url: String,
count: Number,
enabled: Boolean,
items: Array,
config: Object
}
イベントアクション
<div data-action="click->controller#method">
<div data-action="resize@window->controller#layout">
<div data-action="keydown@document->controller#handleKey">
<div data-action="click->ctrl1#method1 click->ctrl2#method2">
カスタムイベント
// Dispatch
const event = new CustomEvent('name:action', {
bubbles: true,
detail: { key: 'value' }
})
this.element.dispatchEvent(event)
// Listen
data-action="name:action->controller#handler"
Outlets
<div data-controller="parent"
data-parent-child-outlet=".child">
<div class="child" data-controller="child"></div>
</div>
static outlets = ['child']
this.childOutlets.forEach(outlet => outlet.method())
ライフサイクルフック
connect() // Element connected to DOM
disconnect() // Element removed from DOM
[name]TargetConnected(element) // Target added
[name]TargetDisconnected(element) // Target removed
[name]ValueChanged(value, oldValue) // Value changed
[name]OutletConnected(outlet) // Outlet connected
[name]OutletDisconnected(outlet) // Outlet disconnected
実装ワークフロー
新しいコントローラーを作成する場合:
- 責務を特定する - このコントローラーが担う単一の目的は何ですか?
- 状態の管理方法を選ぶ - シリアライズできない場合を除き、Values API を使用します
- 静的プロパティを宣言する - values、targets、classes、outlets
- 変更コールバックを実装する - 値の変更に応じて処理します
- connect() を最小限に保つ - サードパーティのセットアップにのみ使用します
- 宣言的なアクションを使う - addEventListener は避けます
- エラーを適切に処理する - リスクのある操作を try-catch で囲みます
- ライフサイクルをテストする - connect/disconnect の動作を確認します
リファクタリングする場合:
- 単一責任を確認する - 複数の関心事を扱っている場合は分割します
- 設定を抽出する - ハードコードされた値をデータ属性に移します
- Values API に変換する - インスタンスプロパティを values に置き換えます
- connect() を簡潔にする - 状態管理とリスナー登録を外に移します
- 継承/ミックスインを使う - 共通の振る舞いを適切に共有します
- コントローラー間の結合を弱める - イベント/outlets を使って連携します
- エラー処理を追加する - ApplicationController の handleError を実装します
よくある避けるべき間違い
- ❌ コントローラー内で CSS クラス、セレクター、ID をハードコードする
- ❌ 状態に values ではなくインスタンスプロパティを使う
- ❌ connect() に状態設定やイベントリスナーを詰め込みすぎる
- ❌ 複数の関心事を扱う「ページコントローラー」を作成する
- ❌ 適切にクリーンアップせずに
addEventListener()を使う - ❌ connect と disconnect で個別に
.bind()を呼び出す - ❌
requestSubmit()ではなくsubmit()を使う - ❌ 基底クラスを拡張するのではなく変更する
- ❌ コントローラー間を密結合にする
- ❌ ログや報告を行わずにエラーを握りつぶす
リソース
このスキルのすべてのパターンは、StimulusJS のベストプラクティスを独自の視点でまとめたコレクションである betterstimulus.com に基づいています。
詳しい説明と例については、以下を参照してください:
references/architecture.md- コントローラー設計パターンreferences/state-management.md- Values API の使用方法references/lifecycle.md- ライフサイクルのベストプラクティスreferences/events-and-interaction.md- コミュニケーションパターンreferences/solid-principles.md- SOLID 設計原則references/dom-and-turbo.md- DOM 操作と Turboreferences/error-handling.md- エラー管理