82
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

完全に理解するTurbo Stream

Last updated at Posted at 2023-12-10

完全に理解するTurbo Streams

概要

  • turbo-rails gemを使用したturbo_streamの実装方法がわかるようになります。
  • Rails6系以前のrails-ujsを使用したAjax機能の代替となるものをTurbo Streamsを使用して作るための説明がメインです。
  • broadcastについては説明しません。

Ajaxの話

Turbo StreamsはRails6系以前のAjaxの実装とイメージが近いのでまずAjaxについて簡単に概要を説明します。
Ajaxは「Asynchronous JavaScript and XML」の略称で、この技術を用いるとページを再読み込みしなくても画面を更新することが出来ます。

Ajaxを理解するにはまず、Webブラウザの基本的な動作について理解しておく必要があります。
ユーザーが何かのリンクをクリックするとブラウザはサーバーに対してリクエストを1つ送信します。
ブラウザは、サーバーから受け取った情報を見て、0からユーザーに見せるための画面を作成します。
0から画面を作成するためサーバーが返す情報はHTMLやCSS・JavaScriptなど画面を作成するために必要なすべての情報が含まれています。

対してAjaxを使用すると、JavaScriptを使って更新したい対象の箇所のみを更新するための情報を要求し、すべての画面ではなく更新したい箇所に必要な画面だけを作成して既存の画面から書き換えることが出来ます。
そのため、サーバーから返す情報量を少なくしたり、画面全体を作成しないので表示速度を早めたりすることが出来ます。

通常の画面の更新

デミグラスハンバーグの画像をおろしハンバーグに変えるのに、画面全てを作成するための情報をサーバーに要求します。
無題のプレゼンテーション (2).png

ヘッダーや他の要素を含めたすべての画面を0から作成します。
orosi_reload.png

AjaxやTurboを使用した画面の更新

デミグラスハンバーグの画像をおろしハンバーグに変えるのに、おろしハンバーグ要素のみを作成するための情報をサーバーに要求します。
demi.png

赤枠で囲まれたおろしハンバーグの要素のみを0作成して既存の画面から書き換えます。
orosi_reload_only.png

Turbo StreamsをRailsのアプリで実装する

前提条件

下記条件はRails7系でrails newを行うと勝手に設定されているかなと思います

  • turbo-railsgemがインストールされていること(viewでturbo_streamを使いやすくなります。入っていなければインストールしてください)
  • app/javascript/application.jsでturboを使う設定が出来ていること

実装方法

ViewからTURBO_STREAMリクエストを送る

getリクエストを送るリンクの場合下記のようにdata: { turbo_stream: true }をつけるとコントローラーでturbo_streamのリクエストとして受け取ることが出来るようになります。

<%= link_to 'Edit', edit_post_path(post), data: { turbo_stream: true } %>

get以外のリクエストの場合下記のようにdata-turbo_methodを指定することでturbo_streamのリクエストを送れます。

  <%= link_to 'Destroy', post, data: { turbo_confirm: 'Are you sure?', turbo_method: :delete } %>

サーバーログに下記のようなログが出ていればOKです。

Processing by PostsController#edit as TURBO_STREAM

Controllerの設定

Controllerの設定は特に必要ありません。

ViewからTURBO_STREAMのリクエストを送れていればアクション名と対応するaction名.turbo_stream.erbファイルを探してくれます。

def edit
  @post = Post.find(params[:id])
  # render :editは書かなくてもedit.turbo_stream.erbファイルを探してくれる
end

または、下記のようにrender turbo_stream: turbo_streamの構文でコントローラー側で実装することも出来ます

def edit
  @post = Post.find(params[:id])
  render turbo_stream: turbo_stream.replace("post_#{@post.id}", partial: "form", locals: { post: @post })
end

Turbo Streams用のViewを用意する

turbo-railsではturbo_stream.アクション 対象要素のID, partial: 表示したい部分テンプレート, locals: 部分テンプレートで使いたい変数のような形でTurbo Streams用のViewファイルを記述することが出来ます。

<%= turbo_stream.update "post_#{@post.id}", partial: "form", locals: { post: @post } %>

またはパーシャルを指定せずに下記のように直接Erbを記述することが出来ます。

<%= turbo_stream.update "post_#{@post.id}" do %>
  <%= form_with model: @post do |f| %>
    <%= f.text_field :body %>
    <%= f.submit %>
  <% end %>
<% end %>

これは下記のように解釈されブラウザに送られます。
下記の要素がブラウザでキャッチされるとapplication.jsで設定したHotwireが動いてDOMを更新したり、削除したりする操作をしてくれます。

<turbo-stream action="アクション" target="対象要素のID">
  <template>
    パーシャルで指定した内容
  </template>
</turbo-stream>

ブラウザのネットワークタブを確認すると上記の形式でレスポンスが返ってきているのを確認できます。
※サンプルコードを簡略化するためHTMLの属性を削っているのでstyleとかが入っています。
turbo_edit.png

複数のturbo_stream

turbo_streamで複数の処理を行いたいときは下記のように一つのViewに複数のturbo_streamの処理を書くことができます。

<%= turbo_stream.replace "post_form_#{@post.id}", partial: "form", locals: { post: @post } %>

<%= turbo_stream.append "error_messages_#{@post.id}" do %>
  <% @post.errors.full_messages.each do |message| %>
    <%= message %>
  <% end %>
<% end %>

Turbo Streamsの7つのアクション+CSSの指定方法

Append

対象のidを持つ要素の内側の一番お尻に要素を追加します。

<%= turbo_stream.append "posts", partial: "post", locals: { post: @post } %>

パーシャルを使わない場合こんな感じで書けます。

<%= turbo_stream.append "posts" do %>
  <div id="<%= dom_id @post %>">
    <%= @post.body %>
    <%= link_to 'Edit', edit_post_path(@post), data: { turbo_stream: true } %>
    <%= link_to 'Destroy', @post, data: { turbo_confirm: 'Are you sure?', turbo_method: :delete } %>
  </div>
<% end %>

15e6fe605ba4f0d50767cfd0c386b038.gif

Prepend

対象のidを持つ要素の内側の先頭に要素を追加します。

<%= turbo_stream.prepend "posts", partial: "post", locals: { post: @post } %>

prepend.gif

Replace

対象のidを持つ要素自体を置き換えます。

<%= turbo_stream.replace "post_#{@post.id}", partial: "post", locals: { post: @post } %>

replace.gif

Update

対象のidを持つ要素の中身を書き換えます。

<%= turbo_stream.update "posts", partial: "post", locals: { post: @post } %>

update.gif

Remove

対象のidを持つ要素を取り除きます。

<%= turbo_stream.remove "post_#{@post.id}" %>

remove.gif

Before

指定した要素の直前に要素を追加します。

<%= turbo_stream.before "posts", partial: "form", locals: { post: @post } %>

before.gif

After

指定した要素の直後に要素を追加します。

<%= turbo_stream.after "posts", partial: "form", locals: { post: @post } %>

after.gif

class属性を指定する方法

アクション名+_allを使うとclass指定することが出来ます。
対象のclassを持つ要素全てを操作します。
classを指定するときはCSSセレクタを使用して.クラス名のように書きます。

<%= turbo_stream.append_all ".content" do %>
  <div>おいちい</div>
<% end %>

turbo_stream_all.gif

参照

https://turbo.hotwired.dev/reference/streams
https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb

最後に

Turbo Streamsはとっても便利なのでこれを機に使ってみてください。
また、今回の記事を書くにあたって簡単な一枚ページのturbo_streamを使ったアプリを作成したので詳しく見たい方は見てみてください
https://github.com/kenchasonakai/turbo_stream_practice

82
52
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
82
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?