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 frameturbo stream ,前者主要是透過 get ,後者則是 get 以外的 http method。

用法基本上是將 turbo frame tagturbo 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: AppendPrependReplaceUpdateRemoveBeforeAfter端視需求使用不同的 action。端視需求使用不同的 action。至於每個 action 在做什麼,可以參考官網說明。

結論

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

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

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


分享