8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Stimulus v3.2.2で追加された機能・変更された振る舞い

Last updated at Posted at 2023-08-08

執筆時点で昨夕Stimulus v3.2.2がリリースされました。

パッチバージョンではありますが、内容がパッチバージョンとは言えないものが多いので一応拾っておこうと思います。

機能の追加・変更

keyboard event filterにPageUp/PageDownが追加された

Stimulusにはもともとキーボードイベントのフィルタリング機構があります。

下記のようにアクション記述のイベント名を書くところのキーボードイベント(keydown,keyup,keypress)にドットで続けてキー名を書けば、対象のキーが押された時だけ実行されるアクションとして定義することができる機能です。

<xxx data-action="keydown.left->tab#prev">

そこにPageUpPageDownのキーをデフォルトで指定できるようになりました

構文中ではpage_up, page_downという名前で指定するようです。

キーボード操作を実現する際、PageUp/PageDown ではなく ArrowUp/ArrowDownを使うケースが多いのですが、Web特有のUIなどでたまに求められることもあるのでデフォルトで用意しよう...ということなのかなと思います。

参考(Feedパターンなど):

Number型のValueの値に_を含めることができるようになった

StimulusにはHTML側からStimulusコントローラ内で参照したい値を受け渡せるようにするためのValuesと呼ばれるAPIがあります。

<div data-controller="foo" data-foo-some-value="123" />
static values = {some: Number}

connect() {
  alert(this.someValue); // 123
}

コントローラ側で指定した型指定(上記のコードだとNumber)に従ってAPI(this.someValue)にアクセスした際にパースされた結果を得ることができるようになっています。

今回の変更はこのHTML側の記述で、Number型の値の記述だけ_を含めることができるようになったというものです。

主に桁を把握しやすく記述するための配慮のようのです

<div data-controller="foo" data-foo-some-value="123_456" />

See the Pen Untitled by Takuya Nakajima (@Takuya-Nakajima) on CodePen.

Clickイベントにもフィルタリング機構が追加された

冒頭の差分紹介時にキーボードイベントのフィルタリングの機構があるという話をしましたが、それと似た機能がクリックイベントにも追加されました。

主にコントロールキーを押しながらのクリックや、シフトキーを押しながらのクリックなどをサポートする目的の変更になります。

対応しているキーはmeta, ctrl, alt, shiftの4キーのようです。

キーの組み合わせは+を使います。

<button type="button"
  data-controller="foo"
  data-action="ctrl+click->foo#notify"
/>

outletで指定されたセレクタが変更された際にoutletAPIの参照するコントローラも対応したものになるようになった。

Stimulusには要素に指定されたコントローラを使役的に操作したい目的でコントローラのインスタンスにアクセスするためのOutletと呼ばれるAPIが存在しています。

下記のコードのようにfooコントローラを割り当てた要素にhogeコントローラを割り当てた要素のセレクタをdata-{自コントローラ}-{参照したいコントローラ}-outlet="{セレクタ}"という記述で指定すると{参照したいコントローラ名}Outletというプロパティ名でアクセスできるようになります。

<div
  data-controller="foo"
  data-foo-hoge-outlet="#hoge"
/>
<div
  id="hoge"
  data-controller="hoge"
/>

このoutlet記述で指定しているセレクタ(上記のコードでは#hoge)が変更された際、これまではその変更が反映されることはなかったのですが、今回の変更によって反映されるようになりました。

See the Pen Untitled by Takuya Nakajima (@Takuya-Nakajima) on CodePen.

afterLoadハンドラ内のthisが未定義だったバグが治った

Stimulusにはコントローラが定義(application.register)されたタイミングでafterLoadと呼ばれる関数を発火させるという機能があります(要素にアタッチするタイミングではないことに注意)

このafterLoad内でのthisundefinedになっていたバグがあったのですが、これが解消されてコントローラクラスを指すようになりました。

afterLoadハンドラの第二引数にapplicationインスタンスが渡るようになった

上述のafterLoadハンドラは第一引数に定義された時のコントローラ名がわたるのですが、今回それに加えapplication(Application.startの返り値)が渡るようになりました。

上述のafterLoadのコンテキスト変更の差分にしれっと入っており意図は語られていないのですが、サンプルコードにapplicationインスタンスを利用した実装があります。

この例では特定のクラスにマッチする要素にコントローラを割り当てたいが、data-controllerというキーワードはapplication.schemaで変更可能なので信頼性を高めるためにapplication.schemaを参照してキーワードを取得する...といった目的で使われてるようです。

稀に嬉しいことがあるのかな〜という感想です。

outletAPIをconnect内で参照した際に、参照したコントローラが未定義になる問題が一定解消した。

多分これが今回の本命のPRなのかなと思います。

要素の解釈順の問題で以下のような構造ではconnectのタイミングでoutletを正しく参照できませんでした。

<div controller="foo" data-foo-hoge-outlet="#hoge">
  <div controller="hoge" id="hoge">
  </div>
</div>
application.register('foo', class extends Controller) {
  static outlets = ['hoge'];
  connect() {
    alert(this.hogeOutlet); // undefined
  }
}

これはコントローラの割り当てが外の要素から始まることに起因しており、fooコントローラがアタッチされたタイミングではまだ#hogeにhogeコントーラがアタッチされていないため、fooのconnect内では参照が失敗するというものになります。

必然といえば必然なのですが、この挙動に対するissueが乱立したこともあって、一定の対応がとられたようです。
対応内容はoutlet参照時に対象のコントローラが存在しなかった場合、子要素を探索して要素をアタッチするという処理を割り込ませるというものになります。

これにより、上記のコードでも正常にhogeコントローラにアクセスできるようになります。

ただ、Stimulusをrailsで利用していたり、またそれを参考にしてautoloadingしている場合、アタッチは非同期的になるのでかわらず動作は保証されないことになります。

ということで本解消というよりは一定の解消を目指した変更という感じでしょうか。

簡単にawaitするための機構を用意するとかそういう対応がとられるのかなと思っていたので少し驚きです。

下記はoutletで参照してるコントローラのidentifierをconnect時に書き出す処理をバージョン比較したものです。

stimulus3.2.1までの挙動

See the Pen Untitled by Takuya Nakajima (@Takuya-Nakajima) on CodePen.

stimulus3.2.2からの挙動

See the Pen Untitled by Takuya Nakajima (@Takuya-Nakajima) on CodePen.

registerActionOptionのコールバックに渡ってくるeventオブジェクトのparamsフィールドが参照できるようになる

Stimulusのイベントのフィルタリング機構にActionOptionと呼ばれるものがあります。

このActionOptionは独自定義することもでき、たとえばdetails要素の開閉イベントのtoggleを、openした時だけに限定してアクションを実行させたい時、以下のようなActionOptionを定義しておくと簡単に実現できます。

ActionOption
application.registerActionOption("open", ({ event }) => {
  if (event.type == "toggle") {
    return event.target.open == true
  } else {
    return true
  }
})
<details
  data-controller="foo"
  data-action="toggle->foo#notify:open"
/>

このActionOptionを定義するregisterActionOption関数は第一引数にActionOption名、第二引数に登録するフィルタリング関数を定義するのですが、このフィルタリング関数に渡る引数オブジェクト内のイベントプロパティに変更がありました。

もともとイベントプロパティにはイベントオブジェクトが渡ってきており、そこに変更はないのですが、StimulusのActionParams(event.params)が利用可能な状態となって渡ってくるようになりました。

See the Pen Untitled by Takuya Nakajima (@Takuya-Nakajima) on CodePen.

registerActionOptionのコールバックに渡ってくるデータにコントローラのインスタンスが追加される

前述のregisterActionOptionのコールバック関数の引数オブジェクトにcontrollerフィールドが追加され、このイベントを受信しているコントローラのインスタンスが渡るようになりました。

<button type="button" data-action="click->some#method:option">

上記のような記述の場合、controllerフィールドにはsomeコントローラのインスタンスが渡ります。

これにより、コントローラ側のvaluesAPIなどにアクセスできるようになるため、より柔軟なフィルタリングが可能になります。

See the Pen Untitled by Takuya Nakajima (@Takuya-Nakajima) on CodePen.

上記はくじ引きアプリケーションです。

あたりくじ(5)を引き当てたときだけalertがでるようなフィルタリングをしており、当たりくじの値はvaluesAPIを使って柔軟に設定できるようになっています。

ただ、この機能を使うと特定のコントローラに依存したActionOptionとなりやすいので、関連性をわかりやすくするために前述のafterLoad内で定義するなどしたほうがよいかもしれませんね。(ちょうどapplicationインスタンスにもアクセスできるようになってるので)

終わりに

やはりどうみてもパッチバージョンの変更じゃないですねw

outletの挙動変更(主にconnect内での動作保証)が注目度の高い変更なのかなと思いますが、autoloader対応など課題は残されているので今後に注目です。

個人的にはActionOptionに渡るイベントオブジェクトのparamsが解釈可能になった変更が一番ありがたい変更だったかな〜と思います。

8
2
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
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?