シンプルな例で体感するHotwire (2)Turbo StreamsでHTMLをCRUDする

Hotwireの機能のうち、Turbo StreamsもTurbo Frames（[前の記事でまとめました](https://qiita.com/shinsuke-kuroki/items/c0cfcdd17c6909a906a3)）と同じくページの一部のHTMLによるページの部分更新を実現したものですが、Turbo StreamsはデータでいうCRUDにあたる、内容の追加・更新・削除をしやすくしてあります。こちらも見てみましょう。

- Railsでの用例の話をします
- 記事の読者はRailsの基礎は知っているものとします
- Rails7.0.4 + Ruby3.1.2 で動作させています

## おおまかな流れ

HTTPに載せて使用する場合は、[Turbo Framesのおおまかな流れ](https://qiita.com/shinsuke-kuroki/items/c0cfcdd17c6909a906a3#%E3%81%8A%E3%81%8A%E3%81%BE%E3%81%8B%E3%81%AA%E6%B5%81%E3%82%8C)と同じく、サーバーがレスポンスで返してきたページの一部のHTMLを使って部分更新をします。なので通信の流れ自体は同じです。これから述べるHTMLの内容の違いに注目しましょう。

Turbo StreamsはWebSocketに載せて使用する事もできます。この場合はサーバーからプッシュされてくるページの一部のHTMLを使って部分更新をします。（この記事では詳しくは扱いません）

## 実装

「ランダムな数字のリスト」というアプリを考えます。

```app/controllers/randoms_controller.rb
class RandomsController < ApplicationController
  def index
  end

  def append
    @random = rand(1000)

    respond_to do |format|
      format.turbo_stream { render partial: 'append' }
      format.html         { redirect_to randoms_url }
    end
  end
end
```

```app/views/randoms/index.html.erb
<div id="randoms">
</div>

<%= form_with url: append_randoms_path do |f| %>
  <%= f.submit '末尾に追加' %>
<% end %>
```

```app/views/randoms/_append.html.erb
<%= turbo_stream.append "randoms" do %>
  <div>
    <p><%= @random %></p>
  </div>
<% end %>
```

ここで重要なことは、

- html,csv,jsonなどと同じようにturbo_streamという新しいファイルタイプを扱う
  - Content-Typeは `text/vnd.turbo-stream.html`
- HTML要素のid属性が軸になっている

ということです。

## 動作

「末尾に追加」ボタンを押すと、以下のように遷移していきます。

![HelloHotwire.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17353/81959f1a-9587-49dc-43cd-2ca35fb1397d.png)
![HelloHotwire.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17353/be3c4929-1c9c-8fec-407a-c2ce84e26b80.png)
![HelloHotwire.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17353/20f5043e-c489-7894-9a8c-1ff89b7d6444.png)

乱数のリストができ、ボタンを押すごとにその末尾に新しい乱数が追加されます。

HTMLの内容を見ていきましょう。まずは最初のページ。

```html
<!DOCTYPE html>
<html>
  <head>
    （省略）
  </head>

  <body>
    <div id="randoms">
    </div>

    <form action="/randoms/append" accept-charset="UTF-8" method="post">
      <input type="hidden" name="authenticity_token" value="L1QHIa7HLjySDpbW1pRtDeXSPzNJcrzZUcqZz9YF94iS2jjuHb64OVjFZo_UlyS1musQ27mngC6GPaD4lgalow" autocomplete="off" />
      <input type="submit" name="commit" value="末尾に追加" data-disable-with="末尾に追加" />
    </form>
  </body>
</html>
```

次に、「末尾に追加」リクエストのレスポンスです。

```html
<turbo-stream action="append" target="randoms">
  <template>
    <div>
      <p>66</p>
    </div>
  </template>
</turbo-stream>
```

このturbo-streamタグの内容に従って、Turbo Streamsは`randoms`というid属性を持った要素にtemplateタグの中身をappend（innerHTMLの末尾に追加）します。

実際にどうなっているかをChromeのデベロッパーツールで見てみると、

![HelloHotwire.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17353/ab809305-4fe7-cd02-5bd5-e8639e9c1d2f.png)

このように、div要素の中に乱数のリストが作られていることがわかります。

## Turbo Streamsでできる動作

今回の実装例ではappend（innerHTMLの末尾に追加）を使いましたが、他にも以下の動作が可能です。

- prepend（innerHTMLの先頭に追加）
- replace（要素をまるごと置き換える）
- update（innerHTMLを置き換える）
- remove（要素を取り除く）
- before（要素の直前に追加）
- after（要素の直後に追加）

これらを使えば、ちょっとしたTODOリスト的なものであれば（JSを書くことなく）作ってしまうことができます。

また、WebSocketと合わせて使えばかんたんなチャットを作るのも難しくないでしょう。

## 終わり

最初は基本的なRailsアプリで作っておいて、ページ全部切り替えてると遅いなーとなるようなところに段階的にTurbo Frames/Streamsを投入していく、というような開発の仕方がやりやすそうです。

次はStimulusについて…たぶん…書きます。

## 参考にした記事

- [HotwireはRailsを「ゼロJavaScript」でリアクティブにできるか？前編（翻訳）](https://techracho.bpsinc.jp/hachi8833/2021_06_09/108495)
- [Turbo Handbook](https://turbo.hotwired.dev/handbook/introduction) 公式です
- [Hotwireとは何なのか？](https://zenn.dev/en30/articles/2e8e0c55c128e0)