REST約束集中最後一項來自第 3.5.3 節(圖 5-8)的隨需程式碼樣式。REST 允許透過下載和執行小程序或腳本形式的程式碼來擴展客戶端功能。這通過減少預先實施所需的功能數量來簡化客戶端。允許在部署後下載功能可以提高系統的可擴展性。但是,它也降低了可見性,因此僅是 REST 中的可選約束。
在超媒體驅動應用程式中,我們討論如何以超媒體驅動的方式建立 Web 應用程式,而不是流行的 SPA 方法,其中它們是JavaScript,並且在網路層級上是RPC 驅動的。
在 HDA 文章中,我們簡要提到了腳本撰寫
在 HDA 中,超媒體 (HTML) 是建構應用程式的主要媒介,這表示
- 與伺服器的所有通訊仍然透過帶有超媒體 (HTML) 回應的 HTTP 請求來管理
- 腳本撰寫主要用於增強應用程式的前端體驗
腳本撰寫擴展了現有的超媒體 (HTML),但不會取代或顛覆 HDA 的基本 REST 式架構。
在本文中,我們想擴展最後的評論,並描述不「取代」或「顛覆」REST 式、超媒體驅動應用程式的腳本撰寫是什麼樣子。這些經驗法則適用於直接編寫以支援 Web 應用程式的腳本,以及通用 JavaScript 程式庫。
超媒體友善腳本撰寫的基本規則是
以下將詳細說明每個規則。
HDA 的首要原則是使用超媒體作為應用程式狀態的引擎。超媒體友善的腳本撰寫方法將遵循此原則。
實際上,這表示腳本撰寫應避免透過網路與伺服器進行非超媒體交換。
因此,一般來說,超媒體友善的腳本撰寫應避免使用 fetch()
和 XMLHttpRequest
,除非伺服器的回應使用某種超媒體(例如 HTML),而不是資料 API 格式(例如純 JSON)。
尊重 HATEOAS 也表示,一般來說,應避免將複雜的狀態儲存在 JavaScript(而不是 DOM)中。
但是,最後的陳述需要加以限定:只要狀態直接支援比純 HTML 允許的更複雜的前端體驗(例如,小工具),就可以將狀態儲存在客戶端 JavaScript 中。
重申 Fielding 關於 REST 中腳本撰寫目的的說法
允許在部署後下載功能可以提高系統的可擴展性。
因此,腳本撰寫是 REST 式系統的合法組成部分,以便允許建立在底層超媒體中未直接實作的其他功能,從而使超媒體(例如 HTML)更具可擴展性。
這種功能的一個很好的例子是 RTF 編輯器:它可能具有非常複雜的編輯器文件 JavaScript 模型,包括選取資訊、突出顯示資訊、程式碼完成等等。但是,此模型應與 DOM 的其餘部分隔離,並且 RTF 編輯器應使用標準超媒體功能將其資訊公開給 DOM。例如,它應使用隱藏的輸入來將編輯器的內容傳達給周圍的 DOM,而不是要求 JavaScript API 呼叫來取得內容。
其想法是使用腳本撰寫來改善超媒體體驗,方法是提供標準超媒體 (HTML) 工具集中沒有的功能,但以與 HTML 配合良好的方式執行,而不是像許多 SPA 框架那樣,將 HTML 降級為較大 JavaScript 應用程式中的單純 UI 描述語言。
請注意,使用超媒體作為應用程式狀態的引擎並不表示您不能有任何客戶端狀態。顯然,上面引用的 RTF 編輯器範例可能具有大量的客戶端狀態。但是,在某些更簡單的情況下,客戶端狀態是合理的,並且與超媒體驅動應用程式完全一致。
考慮一個簡單的顯示/隱藏切換,其中按一下按鈕或錨點會將類別新增至另一個元素,使其可見。
此暫時性的客戶端狀態在超媒體驅動應用程式中是可以接受的,因為該狀態純粹是前端的。沒有系統狀態會透過這種類型的腳本撰寫進行更新。如果要變更系統狀態(也就是說,如果顯示或隱藏元素會影響儲存在伺服器上的資料),則必須使用超媒體交換。
要考慮的關鍵方面是客戶端上更新的任何狀態是否需要與伺服器同步。
如果是,則應使用超媒體交換。如果否,則可以將狀態僅保留在客戶端。
JavaScript 程式庫啟用超媒體友善腳本撰寫的一種極佳方式是讓它擁有豐富的自訂事件模型。
觸發事件的基於 JavaScript 的元件允許以超媒體為導向的 JavaScript 程式庫(例如 htmx)監聽這些事件並觸發超媒體交換。反過來,這使得任何 JavaScript 程式庫都成為潛在的超媒體控制項,能夠透過使用者選取的動作來驅動超媒體驅動應用程式。
一個很好的例子是Sortable.js 範例,其中 htmx 監聽 Sortable.js 觸發的 end
事件
<form class="sortable" hx-post="/items" hx-trigger="end">
<div class="htmx-indicator">Updating...</div>
<div><input type='hidden' name='item' value='1'/>Item 1</div>
<div><input type='hidden' name='item' value='2'/>Item 2</div>
<div><input type='hidden' name='item' value='3'/>Item 3</div>
<div><input type='hidden' name='item' value='4'/>Item 4</div>
<div><input type='hidden' name='item' value='5'/>Item 5</div>
</form>
當拖放完成時,Sortable.js 會觸發 end
事件。htmx 透過 hx-trigger
屬性來監聽此事件,然後發出 HTTP 請求,與伺服器交換超媒體。這會將此 Sortable.js 拖放支援的小工具變成新的、強大的超媒體控制項。
Web 開發中的一個最新趨勢是「孤島」的概念
孤島架構鼓勵在伺服器轉譯的網頁中建立小型的、重點明確的互動區塊。
在需要更複雜的腳本撰寫方法,並且需要與正常超媒體交換機制以外的伺服器進行通訊的情況下,最符合超媒體的方法是使用孤島架構。這將非超媒體元件與超媒體驅動應用程式的其餘部分隔離。
事件是將非超媒體驅動的孤島整合到更廣泛的超媒體驅動應用程式中的一種乾淨方法,允許您將「內部」孤島轉換為「外部」超媒體控制項,就像上面的 Sortable.js 範例一樣。
Deniz Akşimşek 指出,通常將非超媒體孤島嵌入較大的超媒體驅動應用程式中比反向嵌入更容易。
超媒體友善腳本撰寫的最後一條規則是內嵌腳本:直接在超媒體中編寫腳本,而不是將腳本放在外部檔案中。與此處列出的其他規則相比,這是一個有爭議的概念,我們認為它是超媒體友善腳本撰寫的「可選」規則:值得考慮但不是必需的。
一些 HTML 腳本撰寫程式庫採用了這種獨特的腳本撰寫方法,特別是Alpine.js 和 hyperscript。
以下是一些顯示內嵌腳本的 hyperscript 範例
<button _="on click toggle .visible on the next <section/>">
Show Next Section
</button>
<section>
....
</section>
正如它所說的那樣,此按鈕在按一下時會切換 section
元素上的 .visible
類別。
這種內嵌超媒體腳本撰寫方法的主要優點是,在概念上,強調的是超媒體本身,而不是超媒體的腳本撰寫。
將此程式碼與JSX 元件進行比較,其中腳本語言 (JavaScript) 是核心概念,超媒體/HTML 嵌入其中
class Button extends React.Component {
constructor(props) {
// ...
}
toggleVisibilityOnNextSection() {
// ...
}
render() {
return <button onClick={this.toggleVisibilityOnNextSection}>{this.props.text}</button>;
}
}
在這裡,您可以看到 JavaScript 是使用的主要技術,而超媒體/HTML 用作 UI 描述機制。HTML 是超媒體的事實在這種情況下幾乎無關緊要。
話雖如此,內嵌腳本撰寫和 JSX 方法確實有一個共同的優點:兩者都滿足行為局部性 (LoB) 的設計原則。它們都會將行為定位到相關的元素或元件,這使得更容易了解這些元素和元件的功能。
當然,對於內嵌腳本,直接在超媒體中完成的腳本數量應該有一個軟性限制。您不希望使用腳本撰寫來淹沒超媒體,以至於難以理解超媒體文件的「形狀」。
使用呼叫程式庫函數或使用hyperscript 行為之類的技術,允許您使用內嵌腳本撰寫,同時將實作提取到單獨的檔案或位置。
超媒體友善的腳本撰寫並不需要內嵌腳本,但值得考慮作為更傳統的腳本撰寫/超媒體分割的替代方案。
當然,在現實世界中,有許多有用的 JavaScript 程式庫違反了 HATEOAS,並且不觸發事件。這通常使得它們很難適應超媒體驅動應用程式。儘管如此,這些程式庫可能提供在其他地方難以找到的關鍵功能。
在這種情況下,我們提倡實用主義:如果很容易變更程式庫使其符合超媒體,或者以符合超媒體的方式封裝它,這可能是一個不錯的選擇。您永遠不知道,上游作者可能會考慮提取請求以幫助改進他們的程式庫。
但是,如果沒有,並且沒有好的替代方案,那麼就按設計使用 JavaScript 程式庫。
嘗試將不友善於超媒體的函式庫從應用程式的其餘部分隔離出來,但總體而言,不要花費過多的複雜性預算來維持概念上的純粹性:一天的難處一天當就夠了。