Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

Stimulusで知っておきたい10の機能(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。
また、APIドキュメントへのリンクも追加しています。

Stimulusで知っておきたい10の機能(翻訳)

Stimulusは、今あるHTMLで使える控えめなJavaScriptフレームワークとして喧伝されていますが、Stimulusにはおなじみの機能の他にも、あなたの知らない機能や忘れてしまった機能がいくつも詰まっています。

🔗 1: 存在確認用のプロパティ

StimulusのAPI(ターゲット、CSSクラス、値、アウトレット)には、それぞれ存在確認用の属性オプションが必ずあります。つまり、ある属性が利用可能かどうかを以下のようにチェックできます。

これらの属性のブール値(trueまたはfalseを返す)に基づいて、以下のようにロジックを実行できます。

update() {
  if (!this.hasButtonTarget) return;

  this.buttonTarget.classList.add(this.hasButtonClass ? this.buttonClass : "btn");
}

🔗 2: ターゲットでconnectedコールバックやdisconnectedコールバックが使える

Stimulusクラスで使えるライフサイクルメソッドであるconnecteddisconnectedについては、ほとんどの方がご存知でしょう。しかし、[name]TargetConnected()メソッドや[name]TargetDisconnected()メソッドもあるのです([name]はターゲット名のプレースホルダ)。これらは、ターゲットが接続したタイミングや切断したタイミングで呼び出されます。

レスポンスにTurbo Streamsを使う場合、このコールバックには多くの使い道があります。

  • 検索結果の数値を表示するカウンタを更新する
  • 人物のリストに動的に追加されたときに(アルファベット順で)並べ替える
  • 接続したターゲットに応じて読み込み状態を表示/非表示にする

以下のような感じで使います。

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["item", "count"];

  // ...

  itemTargetConnected() {
    this.#updateItemCount();
  }

  itemTargetDisconnected() {
    this.#updateItemCount();
  }

  // private

  #updateItemCount() {
    this.countTarget.textContent = `(${this.itemTargets.length})`;
  }
}

[name]TargetDisconnected()メソッドは、ライフサイクルメソッドであるdisconnect()が呼び出される前に実行される点にご注意ください。

Stimulusのconnectedコールバックやdisconnectedコールバックについてもっと詳しく知りたい方向けに、以下の記事も書きました。

参考: Connected and Disconnected Target Callbacks with Stimulus | Rails Designer

🔗 3: Stimulusのアクションにパラメータを渡す

Stimulusのメソッドに何らかの属性を引数として渡す必要が生じることがあります。これは以下のように簡単に行えます。

<div data-controller="theme">
  <button data-action="theme#update" data-theme-value-param="dark">
    Lights Off
  </button>
</div>

訳注

  • data-action="theme#update": themeコントローラのupdateメソッドを呼び出す
  • data-theme-value-param="dark": themeコントローラのupdateメソッドのvalueパラメータに"dark"を渡す

続いて以下のようにupdateメソッドを書きます。

update({ params: { value } }) {
  this.#setClass(value);
}

渡すパラメータ名はdata-識別子-パラメータ名-paramという構造に従います。パラメータには、StringやNumberからObjectやBooleanに至るまで、任意の型の値を渡せます。詳しくはStimulusのアクションのパラメータを参照してください。

🔗 4: :preventオプションでデフォルトの振る舞いをキャンセルする

アクションではさまざまなオプションを利用できますが、:preventオプションもその1つです。皆さんはおそらくメソッドでevent.preventDefault()を呼び出したことがあるかと思いますが、Stimulusはそのショートカットとして:preventオプションを提供しています。以下の例を見てみましょう。

<input
  type="text"
  data-controller="input"
  data-action="keypress->input#validate:prevent"
  placeholder="Numbers only"
>
export default class extends Controller {
  validate(event) {
    if (/[0-9]/.test(event.key)) {
      event.target.value += event.key
    }
  }
}

:preventオプション(preventDefault())を指定しないと、バリデーションチェックの後でも無効な文字(非数値)が入力に表示されたままになってしまいます。

🔗 5: :stopオプションでプロパゲーションを止める

Stimulusコントローラのアクションやメソッド内で使うevent.stopPropagation()には、:stopというショートカットもあります。:stopオプションを指定すると、イベントがDOM内を駆け上がらないようになります。以下のコード例を見る方がわかりやすいでしょう。

<div data-controller="dropdown" data-action="click@window->dropdown#hide">
  <button data-action="dropdown#toggle:stop">
    Toggle Dropdown
  </button>

  <div data-dropdown-target="menu">
    <a href="/profile">Profile</a>
    <a href="/settings">Settings</a>
    <a href="/logout">Logout</a>
  </div>
</div>

data-action="dropdown#toggle:stop:stopオプションを指定しないと、このクリックイベントがDOM内を駆け上がって、親要素のdata-actionにあるhideアクションにまで達してしまいます。これはよくありません。

「イベントが駆け上がること」や「preventDefaultの意味」について何となくはわかっていても、まだ正確にはわからない方は、ぜひ私の新刊書籍を予約してください↓。

参考: JavaScript for Rails Developers

🔗 6: :selfオプション

アクションのカスタムオプションには、:selfオプションもあります。これは、その要素自身の場合にのみ発火し、子要素では発火しないようにします。

  <div data-controller="dropdown">
    <div data-action="click->dropdown#toggle:self">
      Menu Header

      <ul class="menu-items">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
    </div>
  </div>

上の例にあるtoggleメソッドは、Menu Headerをクリックした場合にのみ発火し、.menu-items(および子要素)をクリックした場合は発火しなくなります。

アクションのカスタムオプションを作ってみたい方は、以下の記事をどうぞ。

参考: Advanced Stimulus: Custom Action Options | Rails Designer

あるいは、私が作ったカスタムのアクションオプションのコレクションであるStimulus-fxもチェックしてみてください。☺️

参考: Stimulus Fx | Rails Designer

🔗 7: CSSクラスを複数追加する

さまざまなCSSクラスを追加・削除したりオンオフしたりしたい場合は、StimulusのクラスAPIがぴったりです。Stimulusは、複数のCSSクラスを一気に追加できます。必要なのはJavaScriptの構文に関するわずかな知識だけです。
以下のようにスプレッド演算子...を使えばできます。

export default class extends Controller {
  static classes = ["scrolling"];

  scroll() {
    this.element.classList.add(...this.scrollingClasses);
  }
}

Stimulusで複数のCSSクラスをオンオフする方法については、以下の記事で詳しく説明しています。

参考: How to Toggle Multiple CSS Classes with Stimulus | Rails Designer

🔗 8: コントローラ要素のネストに注意

Stimulusで使う個別のコントローラは、互いに切り離された形で動作します。つまり、コントローラがアクセスできる対象は、その時点の現在のコンテキストで定義されているターゲットだけなのです。

親のコントローラは、ネストしている子のコントローラにアクセスできませんし、逆に子のコントローラも親のコントローラにはアクセスできません。

タブの中にタブをネストする例をコードで見たい方は以下をどうぞ。

<div data-controller="tabs">
  <div data-tabs-target="panel">
    <!-- このpanelは、親コントローラから認識できる -->
  </div>

  <div data-controller="tabs">
    <div data-tabs-target="panel">
      <!-- このpanelは、ネストしたtabsコントローラからのみ認識できる -->
    </div>
  </div>
</div>

このことにたまたま気づいた人もいるかもしれませんし、さんざんハマった末に知った人もいるかと思いますが、それでも知っておくとよい機能です。

🔗 9: shouldLoadメソッド

モバイルデバイスでのみ有効にしたい機能がある場合や、タッチパネルのあるデバイス専用の機能がある場合は、StimulusコントローラでshouldLoadを使えば以下のように機能を条件付きで読み込めます。

export default class extends Controller {
  static get shouldLoad() {
    return window.innerWidth <= 768 && "ontouchstart" in window
  }
}

このコントローラは、上の条件が一致しない限り登録も読み込みも一切行われないので、コントローラのインスタンスそのものが作成されなくなります

この振る舞いは、connectライフサイクルメソッドにreturn falseを追加するような場合(インスタンスは「作成される」)とは異なることにご注意ください。

🔗 10: afterLoadメソッド

afterLoadメソッドは、Stimulusコントローラが登録されたときに、それが要素で使われているかどうか(接続されているかどうか)にかかわらず直ちに何らかのセットアップコードを実行する必要がある場合に最適です。

これもコードスニペットを見る方が早いでしょう。

export default class extends Controller {
  static afterLoad(identifier, application) {
    // 実行したいことをここに書く
  }
}

正直に言うと、afterLoad()のうまい使い道について長い間考えてきたのですが、これといったものを思いつきません。とはいうものの、前述のshouldLoad()と使い方は多少似ていますし、知っておくとよいメソッドなのでここに書きました。うまい使い道を見つけた方は、ぜひフォームにてお知らせください!

Stimulusで知っておきたい10の知られざる機能の紹介は、以上でおしまいです。
皆さんはいくつ知っていましたか?

関連記事

Railsの技: TailwindスタイルのCSSトランジションをStimulusJSで行う(翻訳)

Railsの技: StimulusJSコントローラからRailsの環境変数にアクセスする(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。