微炭酸ログ

Ruby や Rails を中心に。

Stimulus を学ぶ

Stimulus Handbook を読んだので、かいつまみました。
コードは Handbook のものを載せています(一部改変しています)。

コントローラを用意する

どのコントローラも以下のように定義する。
ファイル名は規約に従ったものにしておく(xxxx_controller.js)。

// app/javascript/controllers/hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  // この中にいろいろ書いていく
}

コントローラをHTMLに接続する

以下のように、HTML側で接続を宣言する。
以下の場合は、div 要素と hello コントローラ(のインスタンス)が接続される。

%div{ data: { controller: 'hello' }}
  -# ...

コントローラが接続されるときに connect() が走る

上記のHTML要素が読み込まれるとき、つまりコントローラが接続されるとき、connect() の中に書いた処理が走る。

import { Controller } from "stimulus"

export default class extends Controller {
  connect() {
    console.log("Hello, Stimulus!")
  }
}

Action Descriptor(アクション記述子)でイベントをトリガーにする

以下のように書くことで、イベントをトリガーにコントローラのメソッドを呼び出せる。

action: 'click->hello#greet' のところがポイント。
action: 'イベント名->コントローラ名#メソッド名' という関係になるように書けばOK。

import { Controller } from "stimulus"

export default class extends Controller {
  // 先ほどの connect() をリネームしただけ
  greet() {
    console.log("Hello, Stimulus!")
  }
}
%div{ data: { controller: 'hello' }}
  %button{ data: { action: 'click->hello#greet' }} Greet

Targets でコントローラからHTML要素を参照する

Targets を宣言し、それに紐づけた要素はコントローラ内で参照できるようになる。

↓の例だと、input 要素を name という名前で参照できるようにしている。

import { Controller } from "stimulus"

export default class extends Controller {
  // Targets の宣言
  static targets = ["name"]

  greet() {
    // 参照するときは this.xxxxTarget と書く
    const element = this.nameTarget

    const name = element.value
    console.log(`Hello, ${name}!`)
  }
}
%div{ data: { controller: 'hello' }}
  -# nameTarget とHTML要素を紐づける
  %input{ type: :text, data: { 'hello-target': 'name' } }
  %button{ data: { action: 'click->hello#greet' }} Greet

Targets で提供されるプロパティ

Targets を宣言すると、先述の this.xxxxTarget のほかに this.xxxxTarget と this.hasXxxxTarget も提供される。

  • this.xxxxTarget:xxxxTarget と最初に紐づけた要素を取得する
  • this.xxxxTargets:xxxxTarget と紐づけた要素を配列で取得する(1つの Target を複数の要素に紐づけることができる)
  • this.hasXxxxTarget:接続している要素の中に xxxxTarget と紐づけた要素があるかどうか(boolean)

Action Descriptor のイベント部分はショートハンドがある

Action Descriptor のイベント部分は、要素の種類によってデフォルト値が決まっている。
そのため、デフォルト値のイベントをそのまま使う場合は記述を省略できる。

先述した以下の例だと、button 要素のデフォルトイベントは click なので、click->hello#greet の click-> の部分は無くてOK。

%div{ data: { controller: 'hello' }}
  -# %button{ data: { action: 'click->hello#greet' }} Greet

  -# これでOK
  %button{ data: { action: 'hello#greet' }} Greet

要素の種類とデフォルトイベントの関係は以下URLを参照されたい。
https://stimulus.hotwire.dev/reference/actions#event-shorthand

Values でHTMLからコントローラに値を渡す

Values を宣言すると、HTMLからコントローラに値を渡すことができる。
URLを渡してコントローラ側で fetch したり、state の初期値をコントローラに渡したりできる。

RailsからHamlレンダリングした値を、HamlからStimulusコントローラに渡すような使い方もありそう。

以下のように、データ型の概念がある。

%div{ data: { controller: 'slideshow', 'slideshow-index-value': 1 }}
  -# ...
// app/javascript/controllers/slideshow_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = ["slide"]
  // ↓Values の宣言
  static values = { index: Number }

  next() {
    this.indexValue++
  }

  previous() {
    this.indexValue--
  }

  // xxxxValueChanged() メソッドを宣言しておけば、Value の値が変わったときに勝手に呼び出される
  indexValueChanged() {
    this.showCurrentSlide()
  }

  showCurrentSlide() {
    this.slideTargets.forEach((element, index) => {
      // this.xxxxValue と書いて参照できる
      element.hidden = index != this.indexValue
    })
  }
}

参考