Stimulus 5: 状態の管理

:::note
この記事は[Google翻訳](https://translate.google.co.jp/)の結果を編集したものです。
:::

https://stimulus.hotwired.dev/handbook/managing-state

最新のフレームワークのほとんどは常にJavaScriptで状態を維持することを推奨しています。それらはDOMを書き込み専用のレンダリングターゲットとして扱い、サーバーからJSONを消費するクライアント側のテンプレートによって調整されます。

Stimulusは別のアプローチを取ります。Stimulusアプリケーションの状態はDOMの属性として存在します。コントローラー自体はほとんどステートレスです。このアプローチにより最初のドキュメント、Ajax リクエスト、Turboへのアクセス、さらには別のJavaScriptライブラリなど、どこからでもHTMLを操作できるようになり、明示的な初期化手順を行わなくても関連するコントローラーが自動的に起動します。

# スライドショーの作成

前の章では要素にクラス名を追加することでStimulusコントローラーがドキュメント内の単純な状態を維持する方法を学びました。しかし、単純なフラグだけでなく値を格納する必要がある場合はどうすればよいでしょうか?

現在選択されているスライドインデックスを属性に保持するスライドショーコントローラーを作成することで、この問題を調査します。

いつものようにHTMLから始めます。

```html
<div data-controller="slideshow">
  <button data-action="slideshow#previous"> ← </button>
  <button data-action="slideshow#next"> → </button>

  <div data-slideshow-target="slide">🐵</div>
  <div data-slideshow-target="slide">🙈</div>
  <div data-slideshow-target="slide">🙉</div>
  <div data-slideshow-target="slide">🙊</div>
</div>
```

それぞれの`slide`ターゲットはスライドショーの1つのスライドを表します。 コントローラーは一度に1つのスライドのみが表示されるようにする責任があります。

コントローラーを下書きしましょう。次のように新しいファイル`src/controllers/slideshow_controller.js`を作成します。

```javascript
// src/controllers/slideshow_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "slide" ]

  initialize() {
    this.index = 0
    this.showCurrentSlide()
  }

  next() {
    this.index++
    this.showCurrentSlide()
  }

  previous() {
    this.index--
    this.showCurrentSlide()
  }

  showCurrentSlide() {
    this.slideTargets.forEach((element, index) => {
      element.hidden = index !== this.index
    })
  }
}
```

コントローラーはメソッド`showCurrentSlide()`を定義します。このメソッドは各スライドターゲットをループし、インデックスが一致する場合に隠し属性を切り替えます。

最初のスライドを表示してコントローラを初期化し、`next()`および`previous()`アクションメソッドで現在のスライドを進めたり巻き戻したりします。

## ライフサイクル コールバックの説明

`initialize()`メソッドは何をしますか？以前に使用した`connect()`メソッドとの違いは何ですか？

これらはStimulusのライフサイクルコールバックメソッドであり、コントローラーがドキュメントに出入りするときに関連する状態を設定または破棄するのに役立ちます。

| メソッド | Stimulusによる呼び出し |
| :-- | :-- |
| initialize() | コントローラーが最初にインスタンス化されたときに1回 |
| connect() | コントローラが DOM に接続されているときはいつでも |
| disconnect() | コントローラーが DOM から切断されたときはいつでも |

ページを再読み込みし、次へボタンで次のスライドに進むことを確認します。

# DOMから初期状態を読み取る

`this.index`プロパティでコントローラーがその状態（現在選択されているスライド）を追跡する方法に注目してください。

ここで最初のスライドではなく2番目のスライドを表示して、スライドショーの1つを開始したいとします。マークアップで開始インデックスをエンコードするにはどうすればよいでしょうか?

1つの方法はHTMLデータ属性を使用して初期インデックスをロードすることです。たとえばコントローラーの要素に`data-index`属性を追加できます。

```html
<div data-controller="slideshow" data-index="1">
```

次に`initialize()`メソッドでその属性を読み取り、整数に変換して`showCurrentSlide()`に渡します。

```javascript
initialize() {
  this.index = Number(this.element.dataset.index)
  this.showCurrentSlide()
}
```

これで作業は完了するかもしれませんが、扱いにくく属性の名前を決定する必要があり、インデックスに再度アクセスしたり、インデックスをインクリメントして結果をDOMに保存したりしたい場合には役に立ちません。

## 値の使用

Stimulusコントローラーはデータ属性に自動的にマップされる型指定された値のプロパティをサポートします。コントローラークラスの先頭に値の定義を追加すると次のようになります。

```javascript
static values = { index: Number }
```

Stimulusは`data-slideshow-index-value`属性に関連付けられた`this.indexValue`コントローラープロパティを作成し、数値変換を処理します。

実際に見てみましょう。 関連するデータ属性をHTMLに追加します。

```html
<div data-controller="slideshow" data-slideshow-index-value="1">
```

次にコントローラーに静的な値の定義を追加し、`initialize()`メソッドを変更して`this.indexValue`をログに記録します。

```javascript
export default class extends Controller {
  static values = { index: Number }

  initialize() {
    console.log(this.indexValue)
    console.log(typeof this.indexValue)
  }

  // …
}
```

ページをリロードし、コンソールに`1`と`Number`が表示されていることを確認します。

## その`静的な値`の行は何ですか?

ターゲットと同様に値と呼ばれる静的オブジェクトプロパティに値を記述して、Stimulusコントローラーで値を定義します。この場合、`index`と呼ばれる単一の数値を定義しました。値の定義の詳細についてはリファレンスドキュメントを参照してください。

次に`initialize()` とコントローラーの他のメソッドを更新して、`this.index`の代わりに`this.indexValue`を使用します。 完了するとコントローラーは次のようになります。

```javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "slide" ]
  static values = { index: Number }

  initialize() {
    this.showCurrentSlide()
  }

  next() {
    this.indexValue++
    this.showCurrentSlide()
  }

  previous() {
    this.indexValue--
    this.showCurrentSlide()
  }

  showCurrentSlide() {
    this.slideTargets.forEach((element, index) => {
      element.hidden = index !== this.indexValue
    })
  }
}
```

ページを再読み込みしWebインスペクターを使用してあるスライドから次のスライドに移動するときに、コントローラー要素の`data-slideshow-index-value`属性が変化することを確認します。

# コールバックの変更

改訂されたコントローラーは元のバージョンを改善していますが、`this.showCurrentSlide()`への繰り返しの呼び出しが目立ちます。コントローラーの初期化時と`this.indexValue`を更新するたびにドキュメントの状態を手動で更新する必要があります。

Stimulusの値変更コールバックを定義して繰り返しをクリーンアップし、インデックス値が変更されるたびにコントローラーがどのように応答するかを指定できます。

まず`initialize()`メソッドを削除し、新しいメソッド`indexValueChanged()`を定義します。 次に`next()`と`previous()`から`this.showCurrentSlide()`への呼び出しを削除します。

```javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "slide" ]
  static values = { index: Number }

  next() {
    this.indexValue++
  }

  previous() {
    this.indexValue--
  }

  indexValueChanged() {
    this.showCurrentSlide()
  }

  showCurrentSlide() {
    this.slideTargets.forEach((element, index) => {
      element.hidden = index !== this.indexValue
    })
  }
}
```

ページを再読み込みしてスライドショーの動作が同じであることを確認します。

Stimulusは初期化時に`indexValueChanged()`メソッドを呼び出し、`data-slideshow-index-value`属性への変更に応答して呼び出します。Webインスペクターで属性をいじることもでき、コントローラーはそれに応じてスライドを変更します。さあ、試してみてください！

# デフォルト設定

静的定義の一部としてデフォルト値を設定することもできます。これは次のように行われます。

```javascript
static values = { index: { type: Number, default: 2 } }
```

コントローラー要素で`data-slideshow-index-value`属性が定義されていない場合、インデックスは2から始まります。他の値がある場合、デフォルトが必要なものとそうでないものを組み合わせて一致させることができます。

```javascript
static values = { index: Number, effect: { type: String, default: "kenburns" } }
```

# まとめと次のステップ

この章では値を使用してスライドショーコントローラーの現在のインデックスを読み込んで永続化する方法を見てきました。

使いやすさの観点から、私たちのコントローラーは不完全です。 最初のスライドを見ているときは、前へボタンは何もしていないように見えます。内部的に`indexValue`は`0`から`-1`に減少します。代わりに値を最後のスライドインデックスにラップアラウンドすることはできますか？（次へボタンにも同様の問題があります。）

次にStimulusコントローラーでタイマーやHTTPリクエストなどの外部リソースを追跡する方法を見ていきます。