Rails: Turbo StreamsとTurbo Framesの使い分けを理解する(翻訳)
Rails Doctrineの第1条にある「プログラマーの幸せのために最適化すること(Optimize for programmer happiness)」は、おそらくRuby on Railsが成功した要因であると個人的に考えています。さらにいえば、Railsの最適化はプログラマー以外の人も対象にしていると言ってよいでしょう(「プログラマーでない人々」がRailsを活用してオンラインビジネスを立ち上げたという話が知られています)。そのかなりの部分が、Railsの「設定より規約(Convention over Configuration: CoC)」のおかげで可能になっています。これについて異論があるかもしれませんが、実際にそうなのです。
「プログラマーの幸せのために最適化すること」は、最終的に次のように説明されています。「幸せのために最適化することは、おそらくRuby on Railsが形成されるうえで最も重要な鍵であり、今後もそうあり続けるでしょう。」これはまさにその通りで、現在もHotwireでこのアイデアを目にすることが可能です。
まずはTurbo Driveから始めてみましょう。Turbo Driveは変更は不要ですぐ使えるようになっており、ページを再読み込みせずに<body>
を差し替えられるようになります。コストなしで使えることは、確実に幸せな気持ちを高めてくれます。
しかしHotwireには、Turbo FramesとTurbo Streamsという切れ味のよい刃物もあります。私は37signalsの記事にある以下の図が大好きです。
出典: https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing/
しかし私はこの図について少しばかり異論があります。この図によればTurbo FramesやTurbo Streamsを使ったときの嬉しみは(Turbo Driveによる)body
の差し替え機能に及ばないことになりますが、それでも私はTurbo FramesやTurbo Streamsを使っていてとても興奮します。実を言えば、私はむしろTurbo FramesとTurbo Streamsの方が好きなのです。ページを正確に変更できますし、バックエンドの計算量やレスポンスのサイズも少なくて済みます。
とは言うものの、やはりTurbo FramesとTurbo Streamsには違いがあるので、本記事では両者の違いを示すことで、皆さんが実際のケースに応じて慎重に使えるようにしたいと思います。
🔗 Turbo Frames
最初はTurbo Framesを見ていきましょう。Turbo Framesは、ページを複数のパーツに切り分けるときに便利です。
Turbo Framesで最も重要な点は、置き換えられるのは1個のフレームのみであるということです(デフォルトでは「HTTPリクエストを行う」フレームが対象です)。
projects
というTurbo Frameの「中に」ボタンが置かれている場合、リクエストの送信後にサーバーが返すレスポンスに2つ以上のフレームが含まれていても、projects
フレームだけが更新されます。Turbo-Frame
リクエストヘッダーを調べてみると、差し替え対象となるフレーム名が表示されていることがわかります。
コンテンツを更新したいフレームの「外に」ボタンが置かれている場合でも、フレームの更新をトリガーできますが、その場合は以下のように更新対象となるフレームを明示的に指定する必要があります。
button_to "Delete", @projects.first, method: :delete, data: {turbo_frame: "projects"}
つまり、Turbo Frameを使うのであれば、常に「既存のフレームのコンテンツを新しいものに差し替える」ことだけに用いるということです。
🔗 Turbo Streams
さて、実は最も紛らわしいのはTurbo Streamsの部分です。
- Turbo Streamsでは、WebSocketは必須ではありません。
Turbo Streamsは、「通常のWebリクエスト」「WebSocket」「SSE(Server Sent Events)」で利用できます。 -
Turbo Streamによるストリーミングは、「WebSocket上でデータをブロードキャストすること」とみなせます。
このフォーマットはAccept
ヘッダーとContent-Type
ヘッダーでも使われる(MIMEタイプはtext/vnd.turbo-stream.html
)ので、サーバーサイドでコンテンツを配信するときにも利用できます。
respond_to do |format|
format.html { redirect_to projects_url }
format.turbo_stream do
render turbo_stream: turbo_stream.remove(dom_id_for(@project))
end
end
Turbo Streamsは、1件のレスポンスで複数のDOM要素を操作できる点がTurbo Framesと異なります。
また、Turbo Streamsでは、要素の単なる差し替え(replace
)の他にも、append
、prepend
、update
、remove
などが使えます。
format.turbo_stream do
render turbo_stream: [
turbo_stream.prepend('ongoing_projects', partial: 'projects/kanban/ongoing_project'),
turbo_stream.remove(dom_id_for(@project))
]
end
🔗 Turbo FramesとTurbo Streamsをどう使い分けるか
最も大事な点を以下にまとめました。
- Turbo Frames:
- 差し替え可能なのは1個のフレームだけなので、レスポンスで複数のフレームを返しても無効です。
- デフォルトでは、HTTPリクエストを送信するフレームが差し替え対象になります。
フレームの外側にあるフレームを置き換え対象にする場合は、フレーム名を指定する必要があります。 - 本質的にフレームの差し替えだけを行います。
- Turbo Streams:
- WebSocketは必須ではありません。
ただしWebSocketを使えば、すべての関係者に更新をリアルタイムでブロードキャスト可能になります。 - MIMEタイプは
text/vnd.turbo-stream.html
です。 - Turbo Framesと異なり、1回のレスポンスで、互いに無関係な複数のページ要素を操作可能です。
- 単なる差し替え以外に、
append
やprepend
、削除などもサポートしています。
- WebSocketは必須ではありません。
両者を表形式で比較してみました。
機能 | Turbo Frames | Turbo Streams |
---|---|---|
更新対象 | 単一要素のみ | 複数要素 |
更新の種類 | 差し替えのみ | append , prepend , replace , update , remove , before , after , refresh |
リアルタイム更新 | 不可 | WebSockets経由で可能だが、単なるボーナス |
Turbo StreamsとTurbo Framesのどちらを使うか悩んだときにこれが役立ちます。質問がありましたらお気軽にメールにてお問い合わせください。
それではまた!
追伸: Hotwire関連の記事をもっとお読みになりたい方は、以下の動画や記事もどうぞ。
- YouTube動画: Hotwire, Turbo Drive, Frames and Streaming. Long Live Server Side Rendering with SPA experience
- YouTube動画: Turbo Streaming AKA Broadcasting over Web Socket explained. DON'T DO THAT MISTAKE!!!
- YouTube動画: How to implement infinite scroll pagination for a table using Rails Hotwire Turbo
- YouTube動画: Make your tables alive with turbo streams. Redirect vs Turbo Streaming. Which one to choose?
- 記事: Rails: HotwireのTurbo Streamsとリダイレクトのどちらを使うか?(翻訳)
- 記事: Rails: '読み込み中'アニメーションをTailwindCSSでTurbo Framesに追加する方法(翻訳)
- 記事: Take advantage of Turbo Streams in event handlers
- 記事: Rails: TurboとViewComponentを使うときの注意点(翻訳)
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。