## HTMX 的核心:觸發器 (Trigger) 與進階控制技術 {#sec-f4f92dc347e1} 在之前的文章中,我們探討了使用 [[HTMX]] 向伺服器發送請求的基本方法。我們也了解到,透過 `hx-get`、`hx-post`、`hx-put`、`hx-delete` 等屬性,即使沒有 JavaScript 的 `fetch()`,也能實現相當多的 Ajax 操作。 然而,在使用 HTMX 的過程中,我們可能會發現,與其控制**發送什麼請求**,不如控制**何時發送請求**更為重要。 這就是 `hx-trigger` 屬性登場的時機。 如果說 `hx-get` 或 `hx-post` 是決定**「要做什麼」**的屬性, 那麼 `hx-trigger` 則是決定**「何時執行」**的屬性。 如果沒有使用 `hx-trigger`,HTMX 可能只會被視為一個簡單的 Ajax 工具,僅在按鈕被點擊時發送請求。但一旦開始使用 `hx-trigger`,你會發現 HTMX 的實用性將大幅提升。 例如,以下這些功能都能在不寫 JavaScript 程式碼的情況下實現: - 僅在輸入停止後才發送搜尋請求 - 防止短時間內的重複點擊 - 以固定週期自動重新整理 - 僅在元素出現在畫面上時才載入 - 僅在特定條件下才發送請求 - 調整不同請求的優先順序,避免衝突 我一開始也曾想:「不就是幾行 `fetch` 程式碼嗎?為什麼要特地用 [[HTMX]]?」當時我將 HTMX 視為一個單純的 Ajax 工具。但在體驗過 `hx-trigger` 強大的控制功能後,我的看法徹底改變了。 在本文中,我將整理 HTMX 的真正核心——**觸發器與進階控制技術**。  --- ## [[HTMX]] 不僅僅是個按鈕工具 {#sec-31cdd005939f} 初次接觸 HTMX 時,通常會從這樣的範例開始: ```html
``` 單看這個,HTMX 可能會給人一種「按下按鈕就發送 Ajax 請求的工具」的感覺。 當然,光是這樣就已經很方便了。但這僅僅是 HTMX 的一部分功能。 真正重要的是,**我們可以在 HTML 中宣告請求發生的時機和條件,而不僅僅是請求本身**。 舉例來說: - 在搜尋欄中每次輸入都向伺服器發送請求,會造成極差的使用者體驗。反之,如果只在輸入停止 500 毫秒後才發送請求,體驗將會自然許多。 - 有些按鈕可能不只是單純點擊,而是希望**在按住 Ctrl 鍵時點擊才觸發**。 - 或者,當捲動頁面,特定元素出現在畫面上時,才希望載入資料。 如果每次遇到這些情況都手動編寫 JavaScript,程式碼將會迅速增長。然而,HTMX 可以在很大程度上**僅透過屬性組合,無需任何 JavaScript 程式碼**來實現這些控制。 這一點確實令人驚訝。**C++ 開發者初次看到 Python 程式碼時,可能會震驚地說:「什麼?不用宣告型別嗎??」我初次接觸 HTMX 的 hx-trigger 時的感受,或許就與這種衝擊類似**。 --- ## 基本觸發器:click、change、submit {#sec-a730147be101} 首先需要了解的是,[[HTMX]] 的設計與 HTML 元素的預設行為相得益彰。 例如,按鈕預設與 `click` 事件關聯,表單預設與 `submit` 事件關聯,而輸入元素則根據情況自然地與 `change` 等事件關聯。 ```html ``` 這個按鈕即使沒有額外指定 `hx-trigger="click"`,在點擊時也會發送請求。 表單也是如此。 ```html ``` 在這種情況下,請求會在表單提交時發生。 換句話說,HTMX 在非常基本的場景中已經表現得相當智慧。但我們真正需要關注的,是從這裡開始的部分。 **超越預設值,直接宣告我們想要的時機和條件。** 這正是我們想要實現的。 --- ## `hx-trigger` 中常用的標準事件 vs. [[HTMX]] 專用觸發器 {#sec-8354ed76dc75} 首先,熟悉下表至關重要。重點是:**瀏覽器原生提供的 DOM 標準事件當然可以使用,此外還有一些 HTMX 專用觸發器。** | 區分 | 值 | 意義 | 常用情境 | 範例 | | ---------- | ----------------- | ---------------------------- | ----------------------------------- | ---------------------------------------------------------------------- | | 標準事件 | `click` | 點擊時發送請求 | 按鈕、連結、執行動作 | `hx-trigger="click"` | | 標準事件 | `input` | 每次輸入值改變時發送請求 | 即時搜尋、自動完成 | `hx-trigger="input changed delay:500ms"` | | 標準事件 | `change` | 值確定且改變時發送請求 | `select`、`checkbox`、失焦後輸入生效 | `hx-trigger="change"` | | 標準事件 | `submit` | 表單提交時發送請求 | 表單提交 | `hx-trigger="submit"` | | 標準事件 | `keyup` | 鍵盤按鍵釋放時發送請求 | 基於鍵盤輸入的搜尋、快速反應 | `hx-trigger="keyup delay:500ms"` | | 標準事件 | `keydown` | 鍵盤按鍵按下時發送請求 | 熱鍵、鍵盤互動 | `hx-trigger="keydown[from:body]"` | | 標準事件 | `mouseup` | 滑鼠按鈕釋放時發送請求 | 拖曳/選擇後反應 | `hx-trigger="mouseup"` | | HTMX 專用 | `load` | 元素載入後立即發送請求 | 延遲載入、填充初始資料 | `hx-trigger="load"` | | HTMX 專用 | `revealed` | 元素出現在畫面上時發送請求 | 無限捲動、懶載入 | `hx-trigger="revealed"` | | HTMX 專用 | `intersect` | 元素與視口交叉時發送請求 | 更精確的懶載入、基於捲動的載入 | `hx-trigger="intersect once"` | | HTMX 專用語法 | `every 5s` | 每隔固定週期發送請求 | 輪詢、狀態更新 | `hx-trigger="every 5s"` | | 自訂事件 | `my-custom-event` | 透過自訂事件發送請求 | 伺服器標頭、JS 整合、鬆耦合事件架構 | `hx-trigger="itemSaved from:body"` | | 修飾符 | `delay:500ms` | 僅在指定時間內沒有額外事件發生時才發送請求 | 去抖動、即時搜尋優化 | `hx-trigger="keyup delay:500ms"` | | 修飾符 | `throttle:1s` | 限制短時間內的重複請求 | 防止重複點擊、抑制過度請求 | `hx-trigger="click throttle:1s"` | | 修飾符 | `once` | 限制僅觸發一次 | 首次載入、一次性事件 | `hx-trigger="intersect once"` | | 修飾符 | `changed` | 僅在值實際改變時才發送請求 | 輸入欄位優化、防止不必要的請求 | `hx-trigger="input changed delay:500ms"` | | 修飾符 | `from:body` | 將事件偵測目標指定為其他元素 | 接收全域事件、處理自訂事件 | `hx-trigger="itemSaved from:body"` | | 修飾符 | `[condition]` | 僅在滿足條件時才發送請求 | 輔助鍵組合、輸入值長度條件 | `hx-trigger="click[ctrlKey]"` / `hx-trigger="keyup[value.length > 1]"` | | 修飾符 | `consume` | 阻止事件傳遞給父級等上層元素 | 防止巢狀 HTMX 請求衝突 | `hx-trigger="click consume"` | | 修飾符 | `queue:first` | 當新事件排隊時僅保留第一個 | 連續輸入中僅保留首次請求 | `hx-trigger="input queue:first"` | | 修飾符 | `queue:last` | 當新事件排隊時僅保留最後一個 | 搜尋欄、自動完成 | `hx-trigger="input queue:last"` | | 修飾符 | `queue:all` | 將所有發生的事件保留在佇列中 | 需要依序處理所有事件時 | `hx-trigger="input queue:all"` | | 修飾符 | `queue:none` | 如果請求正在進行,則忽略新事件 | 完全阻止重複請求 | `hx-trigger="click queue:none"` | `hx-trigger` 的值通常會先寫上**事件 (event)**,必要時再在後面加上**篩選器 (filter)** 和**修飾符 (modifier)** 進行組合。也就是說,我們可以依照**「發生什麼事 (event),在什麼條件下 (filter),以什麼方式處理 (modifier)」**的順序來解讀。其常見形式為 `event[filter] modifier modifier`。 ```html ``` - `keyup` → 鍵盤按鍵釋放時 - `[value.length > 1]` → 僅在輸入值長度大於 1 時 - `changed delay:500ms` → 僅在值改變且 0.5 秒內沒有額外輸入時發送請求 ## 幾個實用範例 {#sec-d893c37bc6f7} 雖然上面已經用表格整理過,但為了不留遺憾,我想再分享幾個我特別喜歡的觸發器範例。 ### 僅在輸入停止後才發送請求:`delay` {#sec-cd31c7256394} 在開發搜尋欄自動完成或即時篩選等功能時,如果使用者每次輸入都發送請求,不僅會增加伺服器負擔,也會讓使用者體驗顯得倉促。 此時使用 `delay` 就能讓體驗流暢許多。 ```html ``` 這段程式碼不會在使用者每次按下按鍵時立即發送請求。相反地,它會**在輸入停止 500 毫秒後**才發送請求。 這實際上就是**去抖動 (debouncing)**。 如果用 JavaScript 實現,需要設定計時器、取消前一個計時器,然後再重新設定,程式碼會比較複雜。但在 [[HTMX]] 中,只需一個屬性就能搞定。 在搜尋、自動推薦、篩選等 UI 中,這項功能幾乎可說是基本配置。 --- ### 防止過於頻繁的發送:`throttle` {#sec-c84ae134ee88} 如果說 `delay` 的感覺是「輸入停止後稍等片刻再發送」,那麼 `throttle` 更接近於「限制在短時間內過於頻繁地發送請求」。 ```html ``` 在這種情況下,即使使用者非常快速地多次點擊按鈕,也能在 1 秒內控制請求不會過度連續發送。 在以下情況中會非常有用: - 防止重複點擊 - 阻止過於頻繁的重複請求 - 減輕伺服器負擔 - 防止誤操作,重複執行相同動作 特別是在「按讚」、「儲存」、「重新整理」、「同步」等按鈕上,這是一個值得考慮的功能。 --- ### 定期自動發送請求:`every` {#sec-0195e4e0d263} 在使用 [[HTMX]] 時,`every` 功能出乎意料地吸引人。 當你希望每隔固定週期從伺服器重新載入特定區域的資料時,無需額外編寫 JavaScript 的輪詢邏輯。 ```html