ゼロから始めるstimulus入門5(外部リソースとの連携)

## はじめに
今回はstimulusで外部リソースとの連携についてドキュメントを見ながら学んでいきます。

過去回↓
第1回：[ゼロから始めるstimulus入門1(ファーストコンタクト)](https://qiita.com/shinry/items/de697c4a679e14a41ffa)
第2回：[ゼロから始めるstimulus入門2(現実的なものを作る)](https://qiita.com/shinry/items/34ca225c098aeb91b160)
第3回：[ゼロから始めるstimulus入門3(古いブラウザを考慮した設計)](https://qiita.com/shinry/items/b2b732b6aeb4374d146d)
第4回：[ゼロから始めるstimulus入門4(状態の管理)](https://qiita.com/shinry/items/b5396cc5f5165940e17d)


## 外部リソースとの連携
コントローラーが外部リソースの状態を追跡しないといけない場合があります。
（「外部」とは、DOMやStimulusの一部ではないものを指す）
例えば、HTTPリクエストを発行し、そのリクエストの状態が変わるごとに応答する必要がある場合があります。また、タイマーを開始し、コントローラーが接続されなくなった時に停止する必要がある場合もあります。
そのような場合のstimulusでの実装をやっていきたいと思います。

## 非同期でHTMLを読み込み、タイマーでリフレッシュする
### content_loader_controller.js
- `static values`でurlとrefreshIntervalを定義
- connect()でload()メソッドとRefreshIntervalValueが存在する場合にstartRefreshing()メソッドを呼び出す
- startRefreshing()メソッドで定期的にリフレッシュするようにする
- load()メソッドでコントローラーが接続されると、要素のdata-content-loader-url-value属性に指定されたURLにFetchリクエストを開始する
```js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = { url: String, refreshInterval: Number }

  connect() {
    this.load()

    if (this.hasRefreshIntervalValue) {
      this.startRefreshing()
    }
  }

  load() {
    fetch(this.urlValue)
      .then(response => response.text())
      .then(html => this.element.innerHTML = html)
  }

  startRefreshing() {
    setInterval(() => {
      this.load()
    }, this.refreshIntervalValue)
  }
}
```

### 呼び出すhtml
routes.rbやcontroller.rbのほうの設定はここでは割愛しますが、呼び出しもとhtmlと同じ階層に、messages.html.erbを作成します。
```html
<ol>
  <li>New Message: Stimulus Launch Party</li>
  <li>Overdue: Finish Stimulus 1.0</li>
</ol>
```

### 呼び出し元html
- ` data-controller="content-loader"`でcontent-loader_controller.jsを設定
- `data-content-loader-url-value="./messages"`で指定されたURLにFetchリクエストを送る
- `data-content-loader-refresh-interval-value="5000"`でコントローラーがコンテンツを再読み込みする頻度をミリ秒単位で指定
```html
<div data-controller="content-loader"
    data-content-loader-url-value="./messages"
    data-content-loader-refresh-interval-value="5000"></div>
```

## 追跡するリソースを解放する
上の場合だと以下のように、タイマーの自動リフレッシュのリクエストが送られ続けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1328154/ee8b92bb-42a7-516e-f455-759136e7d35f.png)
この場合、DOMに接続できない状況でもバックグラウンドでHTTPリクエストを発行し続けます。
その問題に対処するため以下のようにjsを修正します。

### content_loader_controller.js
- startRefreshing()メソッドを修正してタイマーへの参照を保持
    - startRefreshing()メソッドでは、setInterval()関数が返すタイマーIDをthis.refreshTimerというインスタンス変数に保存している。これにより、後でこのタイマーをキャンセルするために利用できる
- タイマーをキャンセルするための対応するstopRefreshing()メソッドを追加
- コントローラーが切断されたときにStimulusにタイマーをキャンセルするように指示するために、disconnect()メソッドを追加
```js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = { url: String, refreshInterval: Number }

  connect() {
    this.load()

    if (this.hasRefreshIntervalValue) {
      this.startRefreshing()
    }
  }

  disconnect() {
    this.stopRefreshing()
  }

  load() {
    fetch(this.urlValue)
      .then(response => response.text())
      .then(html => this.element.innerHTML = html)
  }

  startRefreshing() {
    this.refreshTimer = setInterval(() => {
      this.load()
    }, this.refreshIntervalValue)
  }

  stopRefreshing() {
    if (this.refreshTimer) {
      clearInterval(this.refreshTimer)
    }
  }
}
```

## 複数のソースから情報を読み込む場合
複数の場合は以下のように修正することで、それぞれ読み込むことができます。
load()メソッドを以下に修正します。paramsで値を受け取り、そのparams.urlを読み込みます。
```html
<div data-controller="content-loader">
  <a href="#" data-content-loader-url-param="/messages.html" data-action="content-loader#load">Messages</a>
  <a href="#" data-content-loader-url-param="/comments.html" data-action="content-loader#load">Comments</a>
</div>
```

```js
load({ params }) {
    fetch(params.url)
      .then(response => response.text())
      .then(html => this.element.innerHTML = html)
  }
```

## 最後に
今回は外部リソースとの連携について学ぶことができました。
一旦今回が最後とします。
stimulusを学んでみて、今までjsのアプローチとは違い戸惑うところもありましたが、全体的に整然とした形なので慣れたら使いやすいと思いました。
まだまだ初歩を学んだ程度なので、これからもっと学んでいきます。