「Progressive Application Development with Hotwire」をやっていく
Hotwireのチュートリアル
Progressive Application Development with Hotwire
をやっていく。
シンプルなカウンターを実装していくチュートリアルになっている。
Turbo Drive
リンクをクリックしたり、フォームをSubmitするとTurbo Driveは次のことをおこなう。
- ブラウザにリンクを辿らせない
- History APIを使ってURLを変える
- リクエストはFetchで行う(裏でJSが動く)
- レスポンスを受け取り、
body
をリプレイスする -
head
に変更があればリプレイスし、変更がなければリプレイスしない
Form submissionsについても同じ
Turbo Drive converts Form submissions into fetch requests. Then it follows the redirect and renders the HTML response.
As a result, your browser doesn’t have to reload, and the app feels much faster.
counterをupdateするためのroutesを定義する。
Rails.application.routes.draw do
resources :habits, only: [:show] do
member do
post :plus
post :minus
end
end
end
これでrails routes
すると以下のようなルーティングになる。
plus_habit POST /habits/:id/plus(.:format) habits#plus
minus_habit POST /habits/:id/minus(.:format) habits#minus
member
を使うことで、そのリソースのIDが含まれるパスを作ることができる。この場合、habits
リソースの特定のhabit
に対してplus
,minus
というアクションを定義している。
POST /habits/:id/plus -> HabitsController#plus
POST /habits/:id/minus -> HabitsController#plus
次に、コントローラーにplus
、minus
メソッドを追加している。
個人的にコントローラーに書くメソッド名はindex
、create
、update
、、、などの基本7メソッド名しか書きたくない派だが、チュートリアルなのでそこはスルーしよう。
class HabitsController < ApplicationController
before_action :set_habit
def show; end
def plus
@habit.update(count: @habit.count + 1)
redirect_to @habit
end
def minus
@habit.update(count: @habit.count - 1)
redirect_to @habit
end
private
def set_habit
@habit = Habit.find_by(params[:id])
end
end
After updating the habit, we're redirecting the user to the habit, which renders the show action. It will fetch and render the updated habit. Makes sense?
redirect_to
しているのは、show
を再びrender
するため。そしてデータがフェッチされてbody
がリプレイスされる。SPAっぽくcount
が変わる。
Upon receiving the request on the above endpoint, the minus controller action will decrement the habit count by one and redirect to the show_habit_path(@habit), which returns habits/1.
エンドポイント宛のリクエストを受けて、minus
アクションはhabit.count
をdecrementする。そしてshow_habit_path(@habit)
へリダイレクトする。
このとき、Turbo Driveがリダイレクトのリクエストに従って、habits/1
にfetch
リクエストをする。このリクエストを受診して、show
アクションが呼ばれ、DBから更新されたhabit.count
を取得する。そしてそれをレンダリングする。
Turbo Driveが勝手にSPA風の挙動にしてくれている。改めてこれはすごいよなぁ。
あえてブラウザにリロードさせると違いが顕著にわかる。
app/javascript/application.js
// Turbo Driveをオフる
Turbo.session.drive = false
を書いて実行すると、ブラウザがリロードしているのがわかる。
Turbo Frames
<%= turbo_frame_tag @habit do %>
<div class="text-center font-bold text-gray-700" id="habit-name">
<%= @habit.name %>
</div>
<div class="mt-3 flex justify-center items-center space-x-5">
<%= button_to '-', minus_habit_path(@habit), class: "btn bg-red-300 inline-block shadow-lg" %>
<div class="text-4xl font-bold"><%= @habit.count %></div>
<%= button_to '+', plus_habit_path(@habit), class: "btn bg-green-300 inline-block shadow-lg" %>
</div>
<div class="mt-3 p-2 flex justify-center space-x-1">
<% @habit.count.times do %>
<div class="inline-block border p-1 bg-green-400"></div>
<% end %>
</div>
<% end %>
turbo_frame_tag
で囲むと、そこの中で発生するリンククリックや、Form SubmissionsはTurboにインターセプトされる。そしていつも通り、Fetchリクエストにしてレスポンスを受け取る。dom_id
にマッチしたturbo-frame
要素を抽出して、そこだけを新しいコンテンツと入れ替える。body全体ではなく、マッチしたところだけというのがTurbo Driveとの違い。
Turbo Streams
def plus
@habit.update(count: @habit.count + 1)
render :result
end
def minus
@habit.update(count: @habit.count - 1)
render :result
end
result
テンプレートを呼ぶ👇
app/views/habits/result.turbo_stream.erb
<%= turbo_stream.replace 'habit-count' do %>
<div id="habit-count"><%= @habit.count %></div>
<% end %>
<%= turbo_stream.replace 'habit-markers' do %>
<%= render 'habit_markers', habit: @habit %>
<% end %>
habit count, streak makersをリプレイスする。
app/views/habits/_habit_markers.html.erb
<div id="habit-markers">
<% @habit.count.times do %>
<div class="inline-block border p-1 bg-green-400"></div>
<% end %>
</div>
ボタンを押すと
turbo-strream
タグで囲まれたものだけがレンダーされている。
便利
まとめ
- Turbo Drive
-
body
のみをリプレイスしてくれる - 開発者は何もすることない。デフォルトでこの状態
-
- Turbo Frames
- 1箇所をもっとインタラクティブにしたいならこれ
- Turbo Streams
- 1つのレスポンスから複数の要素をリプレイスしたいならこれ
思想的には、あくまでも最初は何もしなくていい。アプリケーションにインタラクティビティが必要になったらその都度Turbo FramsなりStreamsなりを使えばいい。
ここではStimulusには触れていないのでチュートリアルはここまで。
感想
シンプルだけど、Hotwireのメリットを端的に身につけられるチュートリアルだった。Rails自体の基本的な説明が多く、Railsプログラマはそこはスルーできるので3時間くらいで終えられるんじゃないかな。
実践で使うならもうちょっと濃いチュートリアルをしたほうがいいと思うが、初めの一歩として最適なチュートリアルだと思う。
RailsのフロントエンドにはReactやVueなどのライブラリを採用するケースが多いと思うけど、「実際ここほんとにReactな必要ある?」みたいなプロダクトは多々あると思う。実際、自分が所属している会社では、Reactで実装していた箇所をTurbo系にリプレイスして数百行のコードを削除できたことがあった。
さすが、「一人のためのフレームワーク」という謳い文句に違わない機能だ。