1 Ajax 簡介
為了瞭解 Ajax,您必須首先了解 Web 瀏覽器的作用 一般。
當您在瀏覽器的位址列中鍵入 http://localhost:3000
並點選
“Go”,瀏覽器(您的“客戶端”)向伺服器發出請求。它解析
回應,然後獲取所有關聯的資產,例如 JavaScript 檔案,
樣式表和影象。然後它組裝頁面。如果你點選一個連結,它
執行相同的過程:獲取頁面,獲取資產,將它們放在一起,
向你展示結果。這稱為“請求回應週期”。
JavaScript 還可以向伺服器發出請求,並解析回應。它 還可以更新頁面上的資訊。結合這兩個 權力,JavaScript 編寫者可以製作一個網頁,可以只更新部分 本身,而無需從伺服器獲取整頁資料。這是一個 我們稱之為 Ajax 的強大技術。
例如,下面是一些發出 Ajax 請求的 JavaScript 程式碼:
fetch("/test")
.then((data) => data.text())
.then((html) => {
const results = document.querySelector("#results");
results.insertAdjacentHTML("beforeend", html);
});
此程式碼從“/test”獲取資料,然後將結果附加到元素
id 為 results
。
Rails 為使用此構建網頁提供了相當多的內建支援 技術。您很少需要自己編寫此程式碼。本指南的其餘部分 將向您展示 Rails 如何幫助您以這種方式編寫網站,但它是 所有這些都建立在這個相當簡單的技術之上。
2 不顯眼的 JavaScript
Rails 使用一種稱為“Unobtrusive JavaScript”的技術來處理附加 JavaScript 到 DOM。這通常被認為是最佳實踐 在前端社群中,但您可能偶爾會閱讀教程 演示其他方式。
這是編寫 JavaScript 的最簡單方法。你可能會看到它被稱為 '內聯 JavaScript':
<a href="#" onclick="this.style.backgroundColor='#990000';event.preventDefault();">Paint it red</a>
單擊時,連結背景將變為紅色。問題來了:什麼 當我們有很多想要在點選時執行的 JavaScript 時會發生什麼?
<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';event.preventDefault();">Paint it green</a>
很彆扭吧?我們可以從點選處理程式中提取函式定義, 並將其變成一個函式:
window.paintIt = function(event, backgroundColor, textColor) {
event.preventDefault();
event.target.style.backgroundColor = backgroundColor;
if (textColor) {
event.target.style.color = textColor;
}
}
然後在我們的頁面上:
<a href="#" onclick="paintIt(event, '#990000')">Paint it red</a>
那好一點,但是具有相同的多個連結呢? 影響?
<a href="#" onclick="paintIt(event, '#990000')">Paint it red</a>
<a href="#" onclick="paintIt(event, '#009900', '#FFFFFF')">Paint it green</a>
<a href="#" onclick="paintIt(event, '#000099', '#FFFFFF')">Paint it blue</a>
不是很乾,嗯?我們可以通過使用事件來解決這個問題。我們將新增一個 data-*
屬性給我們的連結,然後繫結一個處理程式到每個連結的點選事件
具有該屬性:
function paintIt(element, backgroundColor, textColor) {
element.style.backgroundColor = backgroundColor;
if (textColor) {
element.style.color = textColor;
}
}
window.addEventListener("load", () => {
const links = document.querySelectorAll(
"a[data-background-color]"
);
links.forEach((element) => {
element.addEventListener("click", (event) => {
event.preventDefault();
const {backgroundColor, textColor} = element.dataset;
paintIt(element, backgroundColor, textColor);
});
});
});
<a href="#" data-background-color="#990000">Paint it red</a>
<a href="#" data-background-color="#009900" data-text-color="#FFFFFF">Paint it green</a>
<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a>
我們稱其為“不引人注目”的 JavaScript,因為我們不再混合我們的 JavaScript 到我們的 HTML 中。我們已經正確地分離我們的關注點,讓未來 改變容易。我們可以通過新增資料輕鬆地向任何連結新增行為 屬性。我們可以通過最小化程式執行我們所有的 JavaScript 並且 聯結器。我們可以在每個頁面上提供整個 JavaScript 包,這 意味著它將在第一個頁面載入時下載,然後快取在 之後的每一頁。許多小好處真的加起來了。
3 內建Helpers
3.1 遠端元素
Rails 提供了一堆用 Ruby 編寫的 view helper 方法來幫助你 在產生 HTML 時。有時,您想為這些元素新增一點 Ajax, 在這些情況下,Rails 得到了您的支援。
由於 Unobtrusive JavaScript,Rails "Ajax helpers" 實際上分為兩個 部分:JavaScript 部分和 Ruby 部分。
除非你禁用了 Asset Pipeline, rails-ujs 提供了 JavaScript 的一半,正常的 Ruby view helpers 新增適當的 標籤到你的 DOM。
您可以在下面閱讀有關觸發處理的不同事件的資訊 應用程式中的遠端元素。
3.1.1 form_with
form_with
是幫助書寫表單的 helper。要在表單中使用 Ajax,您可以
將 :local
選項傳遞給 form_with
。
<%= form_with(model: @article, id: "new-article", local: false) do |form| %>
...
<% end %>
這將產生以下 HTML:
<form id="new-article" action="/articles" accept-charset="UTF-8" method="post" data-remote="true">
...
</form>
注意 data-remote="true"
。現在,表單將由 Ajax 提交而不是
而不是通過瀏覽器的正常提交機制。
不過,您可能不想坐在那裡填滿 <form>
。
您可能想在成功提交後做一些事情。要做到這一點,
繫結到 ajax:success
事件。失敗時,使用 ajax:error
。看看這個:
window.addEventListener("load", () => {
const element = document.querySelector("#new-article");
element.addEventListener("ajax:success", (event) => {
const [_data, _status, xhr] = event.detail;
element.insertAdjacentHTML("beforeend", xhr.responseText);
});
element.addEventListener("ajax:error", () => {
element.insertAdjacentHTML("beforeend", "<p>ERROR</p>");
});
});
顯然,你會想要比這更復雜一點,但這是一個 開始。
3.1.2 link_to
link_to
是幫助產生連結的 helper。它有一個 :remote
選項,你
可以這樣使用:
<%= link_to "an article", @article, remote: true %>
產生
<a href="/articles/1" data-remote="true">an article</a>
您可以繫結到與 form_with
相同的 Ajax 事件。這是一個例子。讓我們
假設我們有一個可以刪除的文章列表
點選。我們會像這樣產生一些 HTML:
<%= link_to "Delete article", @article, remote: true, method: :delete %>
並像這樣編寫一些 JavaScript:
window.addEventListener("load", () => {
const links = document.querySelectorAll("a[data-remote]");
links.forEach((element) => {
element.addEventListener("ajax:success", () => {
alert("The article was deleted.");
});
});
});
3.1.3 button_to
button_to
是幫助您建立按鈕的 helper。它有一個 :remote
選項,你可以這樣呼叫:
<%= button_to "An article", @article, remote: true %>
這會產生
<form action="/articles/1" class="button_to" data-remote="true" method="post">
<input type="submit" value="An article" />
</form>
因為它只是一個 <form>
,所以 form_with
上的所有資訊也適用。
3.2 自定義遠端元素
可以使用 data-remote
自定義元素的行為
屬性而無需編寫一行 JavaScript。您可以指定額外的 data-
屬性來實現這一點。
3.2.1 data-method
啟用超連結總是會導致 HTTP GET 請求。但是,如果您的 應用程式是 RESTful, 一些連結實際上是 actions 更改伺服器上的資料,並且必須 使用非 GET 請求執行。此屬性允許標記此類連結 使用顯式方法,例如“釋出”、“放置”或“刪除”。
它的工作方式是,當連結被啟用時,它會構造一個隱藏的表單
在具有“action”屬性對應的“href”value的文件中
連結,以及對應於 data-method
value 的方法,並提交該表單。
因為使用 GET 和 POST 以外的 HTTP 方法提交表單不是
跨瀏覽器廣泛支援,所有其他 HTTP 方法實際上都是通過
使用 _method
引數中指示的預期方法進行 POST。 Rails
自動檢測並對此進行補償。
3.2.2 data-url
和 data-params
您頁面的某些元素實際上並未引用任何 URL,但您可能希望
它們觸發 Ajax 呼叫。指定 data-url
屬性以及
data-remote
將觸發對給定 URL 的 Ajax 呼叫。你也可以
通過 data-params
屬性指定額外的引數。
這對於在複選框上觸發 action 很有用,例如:
<input type="checkbox" data-remote="true"
data-url="/update" data-params="id=10" data-method="put">
3.2.3 data-type
也可以在執行時顯式定義 Ajax dataType
通過 data-type
屬性請求 data-remote
元素。
3.3 確認
您可以通過新增 data-confirm
來要求使用者額外確認
連結和表單上的屬性。使用者將看到一個 JavaScript confirm()
包含屬性文字的對話方塊。如果使用者選擇取消,則 action
不會發生。
在連結上新增此屬性將在單擊時觸發對話方塊,並新增它 在表單上將在提交時觸發它。例如:
<%= link_to "Dangerous zone", dangerous_zone_path,
data: { confirm: 'Are you sure?' } %>
這會產生:
<a href="..." data-confirm="Are you sure?">Dangerous zone</a>
表單提交按鈕上也允許使用該屬性。這允許您自定義
警告訊息取決於被啟用的按鈕。在這種情況下,
你應該不在表單本身上有 data-confirm
。
3.4 自動禁用
還可以在提交表單時自動禁用輸入
通過使用 data-disable-with
屬性。這是為了防止意外
來自使用者的雙擊,這可能會導致重複的 HTTP 請求
後端可能不會檢測到這樣。屬性的 value 是將要顯示的文字
成為處於禁用狀態的按鈕的新 value。
這也適用於具有 data-method
屬性的連結。
例如:
<%= form_with(model: Article.new) do |form| %>
<%= form.submit data: { disable_with: "Saving..." } %>
<% end %>
這將產生一個表單:
<input data-disable-with="Saving..." type="submit">
3.5 Rails-ujs 事件處理程式
Rails 5.1 引入了 rails-ujs 並刪除了 jQuery 作為依賴項。
因此,Unobtrusive JavaScript (UJS) 驅動程式已被重寫以在沒有 jQuery 的情況下執行。
這些引入會導致請求期間觸發的 custom events
發生小的變化:
對 UJS 事件處理程式的呼叫簽名已更改。
與使用 jQuery 的版本不同,所有自定義事件僅返回一個引數:event
。
在此引數中,有一個附加屬性 detail
,其中包含一組額外引數。
有關之前在 Rails 5 及更早版本中使用的 jquery-ujs
的資訊,請閱讀 jquery-ujs
wiki。
活動名稱 | 額外引數 (event.detail) | 解僱 |
---|---|---|
ajax:before |
在整個ajax業務之前。 | |
ajax:beforeSend |
[xhr,選項] | 在傳送請求之前。 |
ajax:send |
[xhr] | 傳送請求時。 |
ajax:stopped |
當請求停止時。 | |
ajax:success |
[回應,狀態,xhr] | 完成後,如果回應成功。 |
ajax:error |
[回應,狀態,xhr] | 完成後,如果回應是錯誤的。 |
ajax:complete |
[xhr,狀態] | 請求完成後,無論結果如何。 |
用法示例:
document.body.addEventListener("ajax:success", (event) => {
const [data, status, xhr] = event.detail;
});
3.6 可停止事件
您可以通過執行 event.preventDefault()
來停止執行 Ajax 請求
來自處理程式方法 ajax:before
或 ajax:beforeSend
。
ajax:before
事件可以在序列化和序列化之前操作表單資料
ajax:beforeSend
事件對於新增自定義請求標頭很有用。
如果停止 ajax:aborted:file
事件,則預設行為允許
瀏覽器通過正常方式提交表單(即非 Ajax 提交)將是
取消,並且根本不會提交表格。這對
實現您自己的 Ajax 檔案上傳解決方法。
請注意,您應該使用 return false
來防止 jquery-ujs
和
event.preventDefault()
為 rails-ujs
。
4 伺服器端問題
Ajax 不僅僅是客戶端,您還需要在伺服器上做一些工作 一邊支援它。通常,人們喜歡他們的 Ajax 請求返回 JSON 而不是 HTML。讓我們討論實現這一目標需要什麼。
4.1 一個簡單的例子
假設您有一系列使用者想要顯示並提供一個 在同一頁面上建立一個新使用者。您的索引 action controller 看起來像這樣:
class UsersController < ApplicationController
def index
@users = User.all
@user = User.new
end
# ...
索引 view (app/views/users/index.html.erb
) 包含:
<b>Users</b>
<ul id="users">
<%= render @users %>
</ul>
<br>
<%= form_with model: @user do |form| %>
<%= form.label :name %><br>
<%= form.text_field :name %>
<%= form.submit %>
<% end %>
app/views/users/_user.html.erb
部分包含以下內容:
<li><%= user.name %></li>
索引頁的頂部顯示使用者。底部 提供一個表單來建立一個新使用者。
底部表單將呼叫 UsersController
上的 create
action。因為
表單的遠端選項設定為 true,請求將被髮布到
UsersController
作為 Ajax 請求,尋找 JavaScript。為了
為該請求提供服務,您的 controller 的 create
action 看起來像
這:
# app/controllers/users_controller.rb
# ......
def create
@user = User.new(params[:user])
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
format.js
format.json { render json: @user, status: :created, location: @user }
else
format.html { render action: "new" }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
注意 respond_to
塊中的 format.js
:它允許 controller
回應您的 Ajax 請求。然後你就有了相應的
app/views/users/create.js.erb
view 產生實際 JavaScript 的檔案
將在客戶端傳送和執行的程式碼。
var users = document.querySelector("#users");
users.insertAdjacentHTML("beforeend", "<%= j render(@user) %>");
JavaScript view 渲染不做任何預處理,所以你不應該在這裡使用 ES6 語法。
5 渦輪連結
Rails 隨附 Turbolinks 庫, 它使用 Ajax 來加速大多數應用程式中的頁面渲染。
5.1 Turbolinks 的工作原理
Turbolinks 將點選處理程式附加到頁面上的所有 <a>
標籤。如果你的瀏覽器
支援
PushState,
Turbolinks 會向頁面發出 Ajax 請求,解析回應,然後
用回應的 <body>
替換頁面的整個 <body>
。它
然後將使用 PushState 將 URL 更改為正確的 URL,保留
重新整理語義併為您提供漂亮的 URL。
如果要禁用某些連結的 Turbolinks,請新增 data-turbolinks="false"
標籤的屬性:
<a href="..." data-turbolinks="false">No turbolinks here</a>.
5.2 頁面更改事件
你經常想對它進行某種處理 頁面載入。使用 DOM,您可以編寫如下內容:
window.addEventListener("load", () => {
alert("page has loaded!");
});
但是,由於 Turbolinks 覆蓋了正常的頁面載入過程, 這個依賴的事件不會被觸發。如果你的程式碼看起來像 為此,您必須更改程式碼才能執行此操作:
document.addEventListener("turbolinks:load", () => {
alert("page has loaded!");
});
有關更多詳細資訊,包括您可以繫結的其他事件,請檢視 the 渦輪連結 自述檔案。
6 Ajax 中的跨站請求偽造 (CSRF) token
使用其他庫進行ajax呼叫時,需要新增 安全性 token 作為庫中 Ajax 呼叫的預設標頭。要得到 token:
const token = document.getElementsByName(
"csrf-token"
)[0].content;
然後,您可以將此 token 作為 X-CSRF-Token
標題提交給您的
Ajax 請求。您不需要為 GET 請求新增 CSRF token,
只有非 GET 的。
您可以在 安全指南 中閱讀有關跨站點請求偽造的更多資訊。
7 其他資源
以下是一些有用的連結,可幫助您瞭解更多資訊:
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。