Dev Talks

遠看是 Trouble,近看是 Turbo - Turbo 介紹

Mike Lai, Rails 工程師 Feb 19, 2024

遠看是 Trouble,近看是 Turbo - Turbo 介紹

前言

就讓我用這篇文章來介紹,什麼是 Turbo。

什麼是 Turbo

Turbo 是 Rails 7 裡佔有一席之地的功能。Rails 7 將 Hotwire 作為預設的前端框架,其中包誇在內的 Turbo 就是本篇主角。

Turbo 主要是想提高畫面上元素的更新效率,以提升使用者體驗,在一般透過導向方式來替換頁面的網頁設計,使用者能明顯地感受到每一次的換頁帶來的停頓,即便不到一秒,但使用 Turbo 則不會有這樣的停頓,讓使用者在操作時倍感順暢。

如何運作

假如我們現在在 A 頁面,頁面上有通往 B 頁面的連結。

在說明運作之前,我們要先知道,透過 Turbo 所進行的換頁,其實不是換頁,而是仍在同一頁 ( A頁面 ),並改變該頁面上的內容 ( HTML )。

改變的方式則是將原本的 A 頁面內容 ( HTML ) 抽掉,並放入 B 頁面的內容 ( HTML ),所以我們可以知道 Turbo 是透過一整包的 HTML 的流動來改變原頁面上的內容,而非前往一個新的頁面。當 HTML 換好後,如果是整頁換,最後會把瀏覽器上的網址換成從 A 頁面的網址換成 B 頁面的網址,如果是局部換,網址則不會改變。所以我們其實是一直在同一張紙上,不斷地更換紙上的內容。

當我們點擊 link 後,會透過 Ajax 向後端發送 request,並在收到 response,將 response 處理完後,將整包 HTML 渲染到新的頁面上。

Turbo 的種類

主要分為 turbo frame 與 turbo stream ,前者主要是透過 get ,後者則是 get 以外的 http method。

用法基本上是將 turbo frame tag 跟 turbo stream tag 掛上 id ,並將要替換的 HTML 也取上相同 id ,便能完成局部替換。

至於整頁替換,在 Rails 7 預設使用 Turbo 的情況下,每個 link 都會是以 Turbo 的方式進行所謂的換頁。

Turbo Frame

我們直接透過實作來介紹,我現在有一個文章列表的介面如下圖:

post_index

一般來說,當使用者點擊 Edit this post 按鈕時,會被導向編輯文章的表單頁面,但可以藉由 turbo 幫我做到,不需導向,直接在這個介面上顯示表單:

post_edit

透過上圖可以發現,原本的文章被替換成文章編輯表單,而不是被導向到編輯的頁面: 網址沒改變、 New Post 按鈕還在,title Posts 還在。

實作

index.html.erb : 上圖整個文章的頁面

<div>
  <div>
    <h1>Posts</h1>
    <%= link_to "New post", new_post_path %>
  </div>

  <div>
    <%= render @posts %>
  </div>
</div>

_post.html.erb : 在文章頁面中,呈現每篇文章,我們把要被替換掉的區塊,用 turbo_frame_tag 包起來,並給予參數 dom_id(post) ,在瀏覽器上,會轉換成 <turbo-frame id='post_1'> </turbo-frame> 。

<%= turbo_frame_tag dom_id(post) do %>
  <p>
    <strong>Title:</strong>
    <%= post.title %>
  </p>

  <p>
    <strong>Content:</strong>
    <%= post.content %>
  </p>

  <% if action_name != "show" %>
    <%= link_to "Show this post", post %>
    <%= link_to "Edit this post", edit_post_path(post) %>
    <hr>
  <% end %>
<% end %>

post_index_with_turbo

edit.html.erb : 文章編輯頁面,我們一樣用 turbo_frame_tag dom_id(@post) 產生一樣的 <turbo-frame id='post_1'> </turbo-frame> 。

<%= turbo_frame_tag dom_id(@post) do %>
  <div>
    <h2>Editing post</h2>

    <%= form_with(model: @post, class: "contents") do |form| %>
          <div>
            <%= form.label :title, 'title:' %>
            <%= form.text_field :title %>
          </div>

          <div>
            <%= form.label :content, 'content:' %>
            <%= form.text_field :content %>
          </div>

          <div>
            <%= form.submit %>
          </div>
        <% end %>

    <%= link_to "Show this post", @post %>
    <%= link_to "Back to posts", posts_path %>
  </div>
<% end %>

post_edit_with_turbo_frame

如此一來,當我按下, Edit this post 按鈕時,就會透過 Ajax 發出 request,取得 edit_post_path(post) 的 HTML 後,依照 edit_post_path(post) 的 HTML 上的 turbo_frame id,將整包 HTML 帶到相對應的 turbo_frame id 裡,執行替換,所以可以看到,我們的文章列表上的第一篇文章直接變成編輯表單。

post_turbo_interact

Turbo Stream

這裡我們一樣透過實作來講解。延續剛剛的編輯文章。在一般情形,當我們點擊 Update Post 執行更新時,在更新成功時,導回文章列表頁面 redirect_to posts_path,並看到新的資料。既然我們表單已經透過 turbo 讓操作單頁化,那送出表單後,應該也是要用 turbo,流程上才合理。

實作

首先,在 PostsController 的 update action 中表明,我們要用 turbo_stream

class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

    def update
    if @post.update(post_params)
      respond_to do |format|
        format.turbo_stream    <---- this one
      end
    else
      render :edit
    end
  end
end

接著在 views/posts 新增 update.turbo_stream.erb ,並定義 turbo_stream 以及它要操作的 action ,這裡使用 update :

<%= turbo_stream.update dom_id(@post) do %>
  <%= render 'post', post: @post %>
<% end %>

// 意思就是,我要把 block 裡的 HTML,透過 turbo_stream,以 update 方式,來更新 id 同為 `dom_id(@post)` 的 turbo-frame 內的 HTML

如此一來,當編輯表單送出後,在 update action 成功更新後,會依照 update.turbo_stream.erb 裡的 turbo_stream 進行操作。

action

turbo_stream 總共有七個 action: Append 、 Prepend 、 Replace、 Update、 Remove、 Before、 After 。端視需求使用不同的 action。端視需求使用不同的 action。至於每個 action 在做什麼,可以參考官網說明。

結論

今天用這篇文章讓第一次接觸 Turbo 的開發者能夠清楚知道 Turbo 的運作與實作,讓我們來快速 review 一下。

Turbo 讓我們可以做到 SPA,並提升換頁的速率,其中可透過 turbo_frame 與 turbo_stream,進行局部換頁。主要是針對相同 id 的 target 進行匹配並渲染。

希望大家可以透過本篇文章了解 Turbo 以及如何實作 Turbo。


分享