[Hotwire]製作簡易聊天室(Turbo Frame)

Jimmy Xiao
7 min readJul 28, 2022

--

該範例取自 Hotwire 官方影片教學
參考網站: turbo-rails doc, hotrails

練習用專案連結

在現今習慣前後端分離的架構底下 API 以及 Json 的溝通方式成為了主流的方法,但今天要介紹的 Hotwire 卻反其道而行

  • 硬是要傳 HTML 給前端 ^ↀᴥↀ^
  • 硬是要 server side render ヾ(*ΦωΦ)ノ
  • 硬是要第一次快速載入網頁 ლ(=ↀωↀ=)ლ
  • 硬是要很 rails (ↀДↀ)✧

Hotwire 主要由兩個 Javascript 套件整合起來

  • Turbo (Core)
  • Stimulus(optional)

目前可以在 Basecamp 所開發的 HEY 看到這個應用

開發者希望工程師能用最少化手動去寫 Javascript 為此開發出 Hotwire. 我在使用的過程中確實只有新增一個 js 檔案其餘的事情基本上都在 controller, model, view 裡面去完成.

Hotwire 只有在 rails 7 才有提供所以要使用的朋友要注意自己的版本

Turbo/turbo-rails

When you click an eligible link, Turbo Drive prevents the browser from following it, changes the browser’s URL using the History API, requests the new page using fetch, and then renders the HTML response.

基本上 turbo 做的事情就是局部的重新渲染 html,透過 turbo_frame_tag 去建立 <turbo-frame> 以及定義要重新渲染的範圍.要注意的是 turbo_frame_tag 基本上是一對的,一定會有一個 DOM 是打 request 而另一個是負責渲染

# /app/views/rooms/show.html.erb...
<%= turbo_frame_tag "room" do %>
<%= render @room %>
<p>
<%= link_to 'Edit', edit_room_path(@room) %>
<%= link_to 'Back', rooms_path, "data-turbo-frame": "_top" %>
<%= link_to 'Add chat', new_room_message_path(@room), data: { turbo_frame: "new_messages" } %>
</p>
<% end %>
...

turbo_frame_tag 後面的第一個參數會轉變為 id

id 來自於 turbo_frame_tag 的第一個參數

當點擊 Edit 按鈕之後,客戶端會送出 GET edit 的請求,此時伺服器端會回傳 HTML 如下圖,我們可以發現其實他的回傳就是一整頁 edit 的頁面.

edit request 的回傳值 preview

Turbo 會透過 <turbo-frame> 去把要重新渲染的地方抓出來

並且更改原本 <turbo-frame> 的 src

不過除了 turbo_frame_tag 這個 helper 之外,我們也可以透過掛載 data-turbor-frame 去驅動 turbo 為我們做事,下面片段程式碼中的 Add chat 就是透過這樣的方式去打 request 然後再把拿到的 HTML 渲染在下方的 <%= turbo_frame_tag “new_messages” %>

# /app/views/rooms/show.html.erb...
<%= turbo_frame_tag "room" do %>
<%= render @room %>
<p>
<%= link_to 'Edit', edit_room_path(@room) %>
<%= link_to 'Back', rooms_path, "data-turbo-frame": "_top" %>
<%= link_to 'Add chat', new_room_message_path(@room), data: { turbo_frame: "new_messages" } %>
</p>
<% end %>
<%= turbo_frame_tag "new_messages" %>
...

還有一種使用方式是單一節點的 turbo_frame_tag,通常這種時候都會有 src 這個屬性,然後會在頁面掛在之後再去自動打 reqeust.

<%= turbo_frame_tag "new_messages", src: new_room_message_path(@room), target: "_top", loading: "lazy" %>

target 管理的是取代 HTML 的範圍,target 如果是 _top,turbo 就會刷新整個頁面因為他會指向 window,反之如果是用 _self 或是其他名稱的話 turbo 只會作用在定義的 <turbo-frame>.

target refers to another <turbo-frame> element by ID to be navigated when a descendant <a> is clicked. When target="_top", navigate the window.

ref: frame targeting

# /app/views/rooms/show.html.erb...
<%= turbo_frame_tag "room" do %>
<%= render @room %>
<p>
<%= link_to 'Edit', edit_room_path(@room) %>
<%= link_to 'Back', rooms_path, "data-turbo-frame": "_top" %>
<%= link_to 'Add chat', new_room_message_path(@room), data: { turbo_frame: "new_messages" } %>
</p>
<% end %>
<%= turbo_frame_tag "new_messages" %>
...

除此之外你也可以透過設定 loading 去決定 <turbo-frame> 的載入方式,透過設定成 lazy 就可以等到 DOM 被看到的時候才會去打 request.

ref: Frame HTML 屬性 (還有其他屬性等著你去發現)

<%= turbo_frame_tag "new_messages", src: new_room_message_path(@room), target: "_top", loading: "lazy" %>

以上是 Turbo, Turbo frame 以及 turbo-rails 的一些簡介,下一篇會繼續分享 turbo-stream 的部分,透過 WebSocket 做到不用重新整理頁面的 realtime rendering.

--

--