更多資訊請至 rubyonrails.org:

在 Rails 中使用 JavaScript

本指南涵蓋將 JavaScript 功能整合到您的 Rails 應用程式中的選項,包括使用外部 JavaScript 套件的選項,以及如何將 Turbo 與 Rails 搭配使用。

閱讀本指南後,您將了解

  • 如何在不需要 Node.js、Yarn 或 JavaScript Bundler 的情況下使用 Rails。
  • 如何使用 import maps、Bun、esbuild、Rollup 或 Webpack 來建立新的 Rails 應用程式以捆綁您的 JavaScript。
  • Turbo 是什麼,以及如何使用它。
  • 如何使用 Rails 提供的 Turbo HTML 輔助方法。

1 Import Maps

Import maps 可讓您使用邏輯名稱匯入 JavaScript 模組,這些邏輯名稱直接從瀏覽器對應到版本化的檔案。Import maps 是 Rails 7 的預設值,允許任何人使用大多數 npm 套件建立現代 JavaScript 應用程式,而無需轉譯或捆綁。

使用 import maps 的應用程式不需要 Node.jsYarn 即可運作。如果您計劃使用 Rails 和 importmap-rails 來管理您的 JavaScript 依賴項,則無需安裝 Node.js 或 Yarn。

使用 import maps 時,不需要單獨的建置過程,只需使用 bin/rails server 啟動您的伺服器即可。

1.1 安裝 importmap-rails

Importmap for Rails 會自動包含在 Rails 7+ 的新應用程式中,但您也可以在現有的應用程式中手動安裝它

$ bin/bundle add importmap-rails

執行安裝任務

$ bin/rails importmap:install

1.2 使用 importmap-rails 新增 npm 套件

若要將新套件新增到以 import map 驅動的應用程式,請從終端機執行 bin/importmap pin 命令

$ bin/importmap pin react react-dom

然後,像往常一樣將套件匯入 application.js

import React from "react"
import ReactDOM from "react-dom"

2 使用 JavaScript Bundler 新增 npm 套件

Import maps 是新 Rails 應用程式的預設值,但如果您偏好傳統的 JavaScript 捆綁,您可以使用 BunesbuildWebpackRollup.js 建立新的 Rails 應用程式。

若要在新的 Rails 應用程式中使用 bundler 而不是 import maps,請將 --javascript-j 選項傳遞給 rails new

$ rails new my_new_app --javascript=bun
OR
$ rails new my_new_app -j bun

這些捆綁選項都帶有簡單的設定,並透過 jsbundling-rails gem 與資產管道整合。

使用捆綁選項時,請使用 bin/dev 來啟動 Rails 伺服器並建置用於開發的 JavaScript。

2.1 安裝 JavaScript 執行環境

如果您使用 esbuild、Rollup.js 或 Webpack 在 Rails 應用程式中捆綁 JavaScript,則必須安裝 Node.js 和 Yarn。如果您使用 Bun,則只需安裝 Bun,因為它既是 JavaScript 執行環境又是 bundler。

2.1.1 安裝 Bun

Bun 網站上找到安裝說明,並使用以下命令驗證它是否已正確安裝在您的路徑中

$ bun --version

應印出您的 Bun 執行環境的版本。如果它顯示類似 1.0.0 的內容,則表示 Bun 已正確安裝。

如果沒有,您可能需要在目前目錄中重新安裝 Bun 或重新啟動您的終端機。

2.1.2 安裝 Node.js 和 Yarn

如果您使用 esbuild、Rollup.js 或 Webpack,您將需要 Node.js 和 Yarn。

Node.js 網站上找到安裝說明,並使用以下命令驗證它是否已正確安裝

$ node --version

應印出您的 Node.js 執行環境的版本。請確認它大於 8.16.0

若要安裝 Yarn,請依照 Yarn 網站上的安裝說明進行。執行此命令應會印出 Yarn 版本

$ yarn --version

如果它顯示類似 1.22.0 的內容,則表示 Yarn 已正確安裝。

3 在 Import Maps 和 JavaScript Bundler 之間選擇

當您建立新的 Rails 應用程式時,您需要選擇 import maps 和 JavaScript 捆綁解決方案。每個應用程式都有不同的需求,您應該在選擇 JavaScript 選項之前仔細考慮您的需求,因為對於大型、複雜的應用程式來說,從一個選項遷移到另一個選項可能非常耗時。

Import maps 是預設選項,因為 Rails 團隊相信 import maps 在降低複雜性、改善開發人員體驗和提高效能方面的潛力。

對於許多應用程式,尤其是那些主要依賴 Hotwire 堆疊來滿足其 JavaScript 需求的應用程式,import maps 將是長期的正確選擇。您可以在 這裡閱讀更多關於將 import maps 作為 Rails 7 中預設值背後的理由。

其他應用程式可能仍然需要傳統的 JavaScript bundler。指出您應該選擇傳統 bundler 的需求包括

  • 如果您的程式碼需要轉譯步驟,例如 JSX 或 TypeScript。
  • 如果您需要使用包含 CSS 或以其他方式依賴 Webpack 加載器的 JavaScript 程式庫。
  • 如果您絕對確定需要 tree-shaking
  • 如果您將透過 cssbundling-rails gem 安裝 Bootstrap、Bulma、PostCSS 或 Dart CSS。如果您在 rails new 中沒有指定不同的選項,則此 gem 提供的所有選項(Tailwind 和 Sass 除外)都會自動為您安裝 esbuild

4 Turbo

無論您選擇 import maps 或傳統 bundler,Rails 都會隨附 Turbo,以加速您的應用程式,同時大幅減少您需要撰寫的 JavaScript 數量。

Turbo 讓您的伺服器可以直接傳遞 HTML,作為替代方案來取代將 Rails 應用程式的伺服器端簡化為僅僅是一個 JSON API 的主流前端框架。

4.1 Turbo Drive

Turbo Drive 藉由避免在每個導覽請求上進行完整頁面拆解和重建來加速頁面載入。Turbo Drive 是 Turbolinks 的改進和替代方案。

4.2 Turbo Frames

Turbo Frames 允許在請求時更新頁面的預定義部分,而不會影響頁面內容的其餘部分。

您可以使用 Turbo Frames 來建立就地編輯,而無需任何自訂 JavaScript、延遲載入內容,並輕鬆建立伺服器渲染的索引標籤介面。

Rails 提供 HTML 輔助方法,以透過 turbo-rails gem 簡化 Turbo Frames 的使用。

使用此 gem,您可以使用 turbo_frame_tag 輔助方法將 Turbo Frame 新增到您的應用程式,如下所示

<%= turbo_frame_tag dom_id(post) do %>
  <div>
     <%= link_to post.title, post_path(post) %>
  </div>
<% end %>

4.3 Turbo Streams

Turbo Streams 將頁面變更作為 HTML 片段傳遞,這些 HTML 片段包裝在可自我執行的 <turbo-stream> 元素中。Turbo Streams 允許您透過 WebSockets 廣播其他使用者所做的變更,並在表單提交後更新頁面的片段,而無需完整頁面載入。

Rails 提供 HTML 和伺服器端輔助方法,以透過 turbo-rails gem 簡化 Turbo Streams 的使用。

使用此 gem,您可以從控制器動作中渲染 Turbo Streams

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.turbo_stream
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

Rails 會自動尋找 .turbo_stream.erb 視圖檔案,並在找到時渲染該視圖。

Turbo Stream 回應也可以在控制器動作中內聯渲染

def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.turbo_stream { render turbo_stream: turbo_stream.prepend("posts", partial: "post") }
    else
      format.html { render :new, status: :unprocessable_entity }
    end
  end
end

最後,Turbo Streams 可以使用內建的輔助方法從模型或背景工作中啟動。這些廣播可用於透過 WebSocket 連線更新所有使用者的內容,保持頁面內容新鮮並將您的應用程式帶入生活。

若要從模型廣播 Turbo Stream,請結合類似這樣的模型回呼

class Post < ApplicationRecord
  after_create_commit { broadcast_append_to("posts") }
end

在頁面上設定 WebSocket 連線後,應該會像這樣接收更新

<%= turbo_stream_from "posts" %>

5 Rails/UJS 功能的 5 個替代方案

Rails 6 推出了一個名為 UJS (Unobtrusive JavaScript) 的工具。UJS 允許開發人員覆寫 <a> 標籤的 HTTP 請求方法,在執行動作之前加入確認對話框等功能。UJS 是 Rails 7 之前的預設選項,但現在建議改用 Turbo。

5.1 方法

點擊連結總是會產生 HTTP GET 請求。如果您的應用程式是 RESTful 的,有些連結實際上是會更改伺服器上資料的動作,應該使用非 GET 請求來執行。data-turbo-method 屬性允許您使用明確的方法(例如 "post"、"put" 或 "delete")來標記這類連結。

Turbo 會掃描應用程式中的 <a> 標籤,尋找 turbo-method 資料屬性,並在存在時使用指定的方法,覆寫預設的 GET 動作。

例如

<%= link_to "Delete post", post_path(post), data: { turbo_method: "delete" } %>

這會產生

<a data-turbo-method="delete" href="...">Delete post</a>

除了使用 data-turbo-method 來變更連結的方法外,另一種替代方法是使用 Rails 的 button_to helper。基於易用性的考量,對於任何非 GET 動作,實際的按鈕和表單是更佳的選擇。

5.2 確認

您可以透過在連結和表單上新增 data-turbo-confirm 屬性,要求使用者額外確認。在點擊連結或提交表單時,使用者會看到一個 JavaScript confirm() 對話框,其中包含該屬性的文字。如果使用者選擇取消,則不會執行該動作。

例如,使用 link_to helper

<%= link_to "Delete post", post_path(post), data: { turbo_method: "delete", turbo_confirm: "Are you sure?" } %>

這會產生

<a href="..." data-turbo-confirm="Are you sure?" data-turbo-method="delete">Delete post</a>

當使用者點擊「刪除文章」連結時,他們會看到「您確定嗎?」的確認對話框。

此屬性也可以與 button_to helper 一起使用,但必須將其添加到 button_to helper 在內部渲染的表單中。

<%= button_to "Delete post", post, method: :delete, form: { data: { turbo_confirm: "Are you sure?" } } %>

5.3 Ajax 請求

從 JavaScript 發出非 GET 請求時,需要 X-CSRF-Token 標頭。沒有這個標頭,Rails 將不會接受請求。

Rails 需要此權杖來防止跨網站請求偽造 (CSRF) 攻擊。請參閱安全指南以了解更多資訊。

Rails Request.JS 封裝了添加 Rails 所需請求標頭的邏輯。只需從套件匯入 FetchRequest 類別,並實例化它,傳入請求方法、URL、選項,然後呼叫 await request.perform() 並處理回覆即可。

例如

import { FetchRequest } from '@rails/request.js'

....

async myMethod () {
  const request = new FetchRequest('post', 'localhost:3000/posts', {
    body: JSON.stringify({ name: 'Request.JS' })
  })
  const response = await request.perform()
  if (response.ok) {
    const body = await response.text
  }
}

當使用其他函式庫發出 Ajax 呼叫時,您需要自己新增安全性權杖作為預設標頭。若要取得權杖,請查看應用程式視圖中由 csrf_meta_tags 列印的 <meta name='csrf-token' content='THE-TOKEN'> 標籤。您可以執行類似以下的操作

document.head.querySelector("meta[name=csrf-token]")?.content


回到頂部